title: 【python基础之函数】--- 函数入门
date: 2023-12-08 18:50:06
updated: 2023-12-11 14:30:00
description:
cover:
https://home.cnblogs.com/u/dream-ze/
函数的基本使用
- 目前为止,借助之前的学习内容,是已经能开发一些功能简单的小程序了
- 但随着程序功能的增多,代码量随之增大
- 此时仍不加区分地把所有功能的实现代码放到一起,将会使得程序的组织结构不清晰,可读性变差,且程序中需要频繁使用同一功能时,只能重复编写该功能的实现代码,日积月累,程序将变得冗长
- 并且当某一功能需要修改时,又不得不找出所有定义及使用这段功能的地方修改之,管理维护的难度极大
- 好吧,装了半天逼,到底该如何解决提出的这些问题呢?
- 我们完全可以从现实生活中找到简化程序设计的方案:
- 比如一个修理工会事先准备好螺丝刀、锤子等工具,这样在进行修理的过程中
- 需要用到拧螺丝的功能时就直接拿来螺丝刀使用
- 需要用到锤击物体的功能时就直接拿来锤子使用,而无需临时制造。
- 这个例子的核心在于’事先准备好工具’,遇到应用场景时’拿来就用’,。
- 在程序中,具备某一功能的‘工具’指的就是函数,‘事先准备工具’的过程即函数的定义,‘拿来就用’即函数的调用。
【一】什么是函数
在Python中,函数是一种可重复使用的代码块,它接受输入参数、执行操作,并返回一个输出结果。函数可以接受任意数量的输入参数,并且可以不返回任何输出结果。函数可以在程序中的任何地方定义,包括在其他函数内部。当函数被调用时,程序的控制流会转移到函数内部,执行函数中的代码,然后返回到调用函数的代码位置。
Python中的函数定义以关键字def开头,后面是函数名称和包含在圆括号中的参数列表。def 关键字与函数名之间通常是一个空格。冒号(:)的作用是用来表示函数头的结束,从而告诉解释器接下来的代码是函数体,即函数实际要执行的操作。函数体必须缩进,通常使用四个空格或一个制表符进行缩进。函数可以使用关键字return返回一个值,或者在没有返回值的情况下直接结束函数。
函数使用的优缺点
使用函数方法的优点:使用函数可以使代码更加清晰和易于维护。如果我们需要计算多个数字的平方,我们只需要调用函数多次即可。如果我们需要修改计算平方的代码,例如更改乘法为幂运算,使用函数的方式只需要更改函数的代码即可,而不需要修改所有调用该代码块的地方。
不使用函数的缺点:不使用函数,我们需要多次复制和粘贴相同的代码块,这会导致代码重复和难以维护。如果我们需要修改计算平方的代码,例如更改乘法为幂运算,如果不使用函数,我们需要在每个代码块中进行修改,这会非常繁琐。
函数的基本原理
函数是一个代码块,可以在程序中被重复使用。
在Python中,我们使用 def 关键字定义一个函数,并给它一个名称。(函数的命名规则与变量的命名规则相同)
函数通常接受一些输入(称为参数),对它们进行处理,并返回输出结果
【二】函数的定义和调用
- 函数的使用必须遵循’先定义,后调用’的原则。
- 函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。
- 没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’。
- 定义函数的语法
在 Python 中,函数的缩进规则是非常重要的,因为它们定义了函数体的范围和执行方式。函数体必须缩进,以指示该代码块属于函数定义。具体来说,Python 函数的缩进规则如下:
函数的定义必须从行首开始,使用 def 关键字指示。
在 def 关键字后面必须跟随函数名和一对圆括号,其中可以包含参数列表。并且以冒号(:)表明函数定义结束,下面是函数体。
函数体的代码块必须缩进,通常使用四个空格作为缩进级别。在函数定义后面的第一行必须缩进,表示函数体的开始。
函数体中的所有语句都必须使用相同的缩进级别。缩进的语句被视为函数体的一部分。
当函数体结束时,缩进级别必须返回到函数定义的缩进级别。
格式:
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
- def: 定义函数的关键字;
- 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
- 括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
- 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
- """文档描述""": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
- 函数体:由语句和表达式组成;
- return 值:定义函数的返回值,return是可有可无的。
【1】函数的定义
(0)空函数
def login():
pass
- 函数体为pass代表什么都不做,称之为空函数。
- 定义空函数通常是有用的,因为在程序设计的开始,往往是先想好程序都需要完成什么功能,然后把所有功能都列举出来用pass充当函数体“占位符”,这将使得程序的体系结构立见,清晰且可读性强。
- 例如要编写一个ftp程序
- 我们可能想到的功能有用户认证,下载,上传,浏览,切换目录等功能
- 可以先做出如下定义:
def auth_user():
"""user authentication function"""
pass
def download_file():
"""download file function"""
pass
def upload_file():
"""upload file function"""
pass
def ls():
"""list contents function"""
pass
def cd():
"""change directory"""
pass
(1)无参无返回值
def interactive():
user=input('user>>: ').strip()
pwd=input('password>>: ').strip()
(2)有参无返回值
- 参数是函数的调用者向函数体传值的媒介
def greet(name):
print(f'Hello,{name}!')
greet('jack') # Output: Hello,jack!
(3)有多个参数无返回值
def add_num(a,b):
result = a + b
print(f'The sum of {a} and {b} is {result}')
add_num(18,20) # Output: The sum of 18 and 20 is 38
(4)多个参数一个返回值
# 定义一个函数,函数有两个参数
def add_numbers(a, b):
result = a + b
print(f"The sum of {a} and {b} is {result}")
# 使用 return 关键字将想要返回的结果返回出去
return result
res = add_numbers(3, 5) # Output: The sum of 3 and 5 is 8
print(res) # 8
#此处一共输出两个地方,一个是res = add_numbers(3, 5) 输出为‘The sum of 3 and 5 is 8’
# 另一个是print(res) 输出为 8
(5)多个参数多个返回值
def rectangle_info(length,width):
area = length * width # 计算面积
perimeter = 2 * (length + width) #计算周长
return area, perimeter #(return 输出面积和周长)
area, perimeter = rectangle_info(9,8) #解压赋值 -----(赋予rectangle_info()变量里的length、width的值)
# (这里的area、perimeter是自定义,也可以定义为其他的函数,但是后面是用当前定义的函数)
print(f'Area:{area},Perimeter:{perimeter}') #输出面积和周长的具体数值
#Output: Area:72,Perimeter:34
【2】函数的三种调用方式
(1)直接调用
def add(x, y):
return x + y
# 直接调用函数,拿到对应的结果
result = add(3, 5)
print(result) # 8
(2)表达式调用
def subtract(x, y):
return x - y
# 将定义好的函数地址给对应的变量
operation = subtract
# 通过给定的变量进行调用
result = operation(10, 5)
print(result) # 5
def add(a, b):
res = a + b
print(f"这是 add 函数 :>>> {res}")
return res
def subtract(a, b):
res = a - b
print(f"这是 subtract 函数 :>>> {res}")
return res
func_dict = {1: add, 2: subtract}
func_id = input("请输入功能 id :>>>> ")
func = func_dict.get(int(func_id))
a = input("请输入参数 a :>>>> ")
b = input("请输入参数 b :>>>> ")
res = func(int(a), int(b))
print(res)
(3)函数作为参数
# 【3】触发这个函数 带入参数x 参数y
def multiply(x, y):
# 计算出返回值
return x * y
def operate(func, x, y):
# 【2】函数地址传过来 带参数过来
# 返回值是 函数地址(参数x,参数y)
# 【4】接收到返回值并返回
return func(x, y)
# 【1】operate 函数 有3个参数 : 函数地址 参数x 参数y
result = operate(multiply, 4, 6)
# 【5】拿到返回值
print(result)
【三】函数的参数
【1】形参和实参的介绍
- 函数的参数分为形式参数和实际参数,简称形参和实参
- 形参即在定义函数时,括号内声明的参数。
- 形参本质就是一个变量名,用来接收外部传来的值。
- 实参即在调用函数时,括号内传入的值
- 值可以是常量、变量、表达式或三者的组合:
- 形参即在定义函数时,括号内声明的参数。
def add(x, y):
return x + y
# 1:实参是常量
res_first = add(1, 2)
print(res_first) # 3
# 2:实参是变量
x = 5
y = 6
res_second = add(x, y)
print(res_second) # 11
# 3:实参是表达式
res_third = add(10 * 2, 10 * add(3, 4))
print(res_third) # 90
# 4:实参可以是常量、变量、表达式的任意组合
a = 2
res_forth = add(a, 10 * add(3, 4))
print(res_forth) # 72
- 在调用有参函数时,实参(值)会赋值给形参(变量名)。
- 在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
【2】位置参数
-
位置即顺序,位置参数指的是按顺序定义的参数
-
需要从两个角度去看:
- 在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参
- 凡是按照这种形式定义的形参都必须被传值
(1)有位置参数而不传值会报错
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
register() # TypeError:缺少3个位置参数
'''
Traceback (most recent call last):
File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/day12.py", line 266, in <module>
register() # TypeError:缺少3个位置参数
TypeError: register() missing 3 required positional arguments: 'name', 'age', and 'sex'
'''
#改:
register('jack',18,'male') # 3个位置参数补充
#Output: Name:jack Age:18 Sex:male
(2)不按位置传参会导致变量错误
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
register(18, "男", "jack")
# Name:18 Age:男 Sex:jack
(3)对应位置传参
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
register("jack", 18, "男")
# Name:jack Age:18 Sex:男
(4)小结
- 在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参
- 凡是按照这种形式定义的实参会按照从左到右的顺序与形参一一对应
【3】关键字参数
在调用函数时,实参可以是key=value的形式,称为关键字参数
- 凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值
(1)按顺序传参
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
# 根据指定关键字进行传参,不能多传参数,也不能少传参数
register(name="Dream", age=18, sex="男")
# Name:Dream Age:18 Sex:男
(2)不按顺序传参
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
# 根据指定关键字进行传参,可以不按顺序传参
register(sex="男", name="Dream", age=18)
# Name:Dream Age:18 Sex:男
(3)位置和关键字混合使用
- 需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用
- 但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
# 定义位置形参:name,age,sex,三者都必须被传值
def register(name, age, sex):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
# 可以使用关键字和位置参数混合传参,但是一定要位置参数在前,关键字参数在后
register("jack", age=18, sex="男")
# Name:jack Age:18 Sex:男
【4】默认参数
- 在定义函数时,就已经为形参赋值,这类形参称之为默认参数
- 当函数有多个参数时,需要将值经常改变的参数定义成位置参数,而将值改变较少的参数定义成默认参数。
- 例如编写一个注册学生信息的函数
- 如果大多数学生的性别都为男,那完全可以将形参sex定义成默认参数
(1)默认参数不传值
# 定义位置形参:name,age 必须被传值 sex默认值为 男
def register(name, age, sex="男"):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
# 可以使用关键字和位置参数混合传参,但是一定要位置参数在前,关键字参数在后
# 参数有默认值,可以不传值
register("jack", age=18)
# Name:jack Age:18 Sex:男
(2)默认参数传值
[1]默认参数初始化
- 默认参数必须在位置参数之后
- 默认参数的值仅在函数定义阶段被赋值一次
# 定义位置形参:name,age 必须被传值 sex默认值为 男
def register(name, age, sex="男"):
print('Name:%s Age:%s Sex:%s' % (name, age, sex))
# 可以使用关键字和位置参数混合传参,但是一定要位置参数在前,关键字参数在后
# 参数有默认值,重复传值会覆盖掉默认值
register("jack", age=18, sex="女")
# Name:jack Age:18 Sex:女
[2]默认参数为可变数据类型
- 默认参数的值通常应设为不可变类型
- 每次调用是在上一次的基础上向同一列表增加值
def append_list(num, list_inner=[]):
list_inner.append(num)
return list_inner
# 传入参数 1 ,对应在函数中的空列表参数中添加值
res_1 = append_list(1)
print(res_1) # 输出:[1]
# 由于列表是可变数据类型,所以在上面的基础上,再添加值会添加到上一个列表内
res_2 = append_list(2)
print(res_2) # 输出:[1, 2] ---追加
# 同理
res_3 = append_list(3)
print(res_3) # [1, 2, 3]
[3]默认参数为空
- 默认参数的值通常应设为不可变类型
def append_list(num, list_inner=None):
if list_inner is None:
list_inner = []
list_inner.append(num)
return list_inner
# 传入参数 1 ,对应在函数中的参数为空
# 在函数内部会有一个判断,不传值为空,则列表重置为空
res_1 = append_list(1)
print(res_1) # [1]
# 在函数内部会有一个判断,不传值为空,则列表重置为空
res_2 = append_list(2)
print(res_2) # [2]
# 在函数内部会有一个判断,不传值为空,则列表重置为空
res_3 = append_list(3)
print(res_3) # [3]
【5】可变长参数
- 参数的长度可变指的是在调用函数时,实参的个数可以不固定
- 而在调用函数时,实参的定义无非是按位置或者按关键字两种形式
- 这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
(1)可变长位置参数(*args)
[1]任意类型参数
- 如果在最后一个形参名前加
*
号,那么在调用函数时,溢出的位置实参,都会被*
接收,以元组的形式保存下来赋值给该形参
# 在最后一个形参名args前加*号
def foo(x, y, z=1, *args):
print(x)
print(y)
print(z)
print(args)
foo(1, 2, 3, 4, 5, 6, 7)
# 实参1、2、3按位置为形参x、y、z赋值
# 多余的位置实参4、5、6、7都被*接收,以元组的形式保存下来,赋值给args
# 即args=(4, 5, 6,7)
- 如果形参为常规的参数(位置或默认),实参仍可以是
*
的形式
def foo(x, y, z=3):
print(x)
print(y)
print(z)
foo(*[1, 2]) # 等同于foo(1,2)
#输出
1
2
3
- 如果我们想要求多个值的和,
*args
就派上用场了
def add(*args):
res = 0
for i in args:
res += i
return res
res = add(1, 2, 3, 4, 5)
print(res) # 15
[2]解包
- 如果我们事先生成了一个列表,仍然是可以传值给
*args
的
def foo(x, y, *args):
print(x)
print(y)
print(args)
L = [3, 4, 5]
foo(1, 2, *L)
# *L就相当于位置参数3,4,5 ----> 解包
# 对应到函数中就是,foo(1,2,*L)就等同于foo(1,2,3,4,5)
- 注意:如果在传入 L 时没有加
*
,那 L 就只是一个普通的位置参数了
def foo(x, y, *args):
print(x)
print(y)
print(args)
L = [3, 4, 5]
foo(1, 2, L)
# 如果在传入 L 时没有加 `*` ,那 L 就只是一个普通的位置参数了
# ([3, 4, 5],) ----> 为什么有一个 , ?
(2)可变长关键字参数(**kwargs)
- 如果在最后一个形参名前加
**
号,那么在调用函数时,溢出的关键字参数,都会被**
接收,以字典的形式保存下来赋值给该形参
def foo(x, **kwargs): # 在最后一个参数kwargs前加**
print(x)
print(kwargs)
foo(y=2, x=1, z=3)
# 溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
# 1
# {'y': 2, 'z': 3}
- 如果我们事先生成了一个字典,仍然是可以传值给
**kwargs
的
def foo(x, y, **kwargs):
print(x)
print(y)
print(kwargs)
dic = {'a': 1, 'b': 2}
foo(1, 2, **dic)
# **dic就相当于关键字参数a=1,b=2,foo(1,2,**dic)
# 等同foo(1,2,a=1,b=2)
# 1
# 2
# {'a': 1, 'b': 2}
- 如果在传入 dic 时没有加
**
,那 dic 就只是一个普通的位置参数了
def foo(x, y, **kwargs):
print(x)
print(y)
print(kwargs)
dic = {'a': 1, 'b': 2}
foo(1, 2, dic)
'''
Traceback (most recent call last):
File "/Users/xxx/Downloads/笔记/pythonProject/py-days-test/day12.py", line 269, in <module>
foo(1, 2, dic)
TypeError: foo() takes 2 positional arguments but 3 were given
'''
- 如果形参为常规参数(位置或默认),实参仍可以是
**
的形式
def foo(x, y, z=3):
print(x)
print(y)
print(z)
foo(**{'x': 1, 'y': 2}) # 等同于foo(y=2,x=1)
(3)组合使用
def register(name, age, *args, **kwargs):
user_dict = {'name': name, 'age': age}
if args:
user_dict['additional_info'] = args
if kwargs:
user_dict.update(kwargs)
return user_dict
user_dict = register("jack", 18, "handsome", hobby=["rap"])
print(user_dict)
# {'name': 'jack', 'age': 18, 'additional_info': ('handsome',), 'hobby': ['rap']}
【6】命名关键字参数
(1)函数内判断
- 在定义了
**kwargs
参数后,函数调用者就可以传入任意的关键字参数key=value - 如果函数体代码的执行需要依赖某个key,必须在函数内进行判断
def register(name, age, **kwargs):
if 'sex' in kwargs:
# 有sex参数
pass
if 'height' in kwargs:
# 有height参数
pass
(2)命名关键字传参
- 想要限定函数的调用者必须以key=value的形式传值
- Python3提供了专门的语法:+
- 需要在定义形参时,用
*
作为一个分隔符号,*
号之后的形参称为命名关键字参数。
- 需要在定义形参时,用
- 对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值
- Python3提供了专门的语法:+
def register(name, age, *, sex, height): # sex,height为命名关键字参数
pass
# 正确使用
register('jack', 18, sex='male', height='1.8m')
# TypeError:未使用关键字的形式为sex和height传值
register('jack', 18, 'male', '1.8m')
# TypeError没有为命名关键字参数height传值。
register('jack', 18, height='1.8m')
[3]命名关键字参数和命名关键字参数默认值
- 命名关键字参数也可以有默认值,从而简化调用
def register(name, age, *, sex='male', height):
print('Name:%s,Age:%s,Sex:%s,Height:%s' % (name, age, sex, height))
register('jack', 18, height='1.8m')
# Name:jack,Age:18,Sex:male,Height:1.8m
- 需要强调的是:sex不是默认参数,height也不是位置参数
- 因为二者均在
*
后,所以都是命名关键字参数,形参sex=’male’属于命名关键字参数的默认值,因而即便是放到形参height之前也不会有问题。 - 另外,如果形参中已经有一个
*args
了,命名关键字参数就不再需要一个单独的*
作为分隔符号了
- 因为二者均在
def register(name, age, *args, sex='male', height):
print('Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s' % (name, age, args, sex, height))
# sex与height仍为命名关键字参数
register('jack', 18, 1, 2, 3, height='1.8m')
# Name:jack,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m
【7】混合使用
- 综上所述所有参数可任意组合使用,但定义顺序必须是:
位置参数
、默认参数
、*args
、命名关键字参数
、**kwargs
- 可变参数
*args
与关键字参数**kwargs
通常是组合在一起使用的 - 如果一个函数的形参为
**args
与**kwargs
,那么代表该函数可以接收任何形式、任意长度的参数
def wrapper(*args,**kwargs):
pass
- 在该函数内部还可以把接收到的参数传给另外一个函数(这在装饰器的实现中大有用处)
def func(x, y, z):
print(x, y, z)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
wrapper(1, z=3, y=2)
# 1 2 3
- 提示:
*args
、**kwargs
中的args和kwargs被替换成其他名字并无语法错误,但使用args、kwargs是约定俗成的。