一、函数定义
1. 定义函数的语法
Python自定义函数的语法结构如下:
def 函数名(形式参数):
函数体
return 返回值
其中:def为自定义函数的关键词;函数名为自定义函数的名称,需遵循标识符定义规则和约定;形式参数(非必须),是函数接受外部信息的“接口”。return为返回结果的关键字,返回值(非必须),Python函数可以没有return语句返回结果,也可以只有return空语句。
说明:形式参数,简称形参,是函数接受外部数据的“接口”。形参不是必须的,当函数无需外部输入就可以得出结果时,可以没有形参,也称无参函数;当函数需要接受外部输入的数据(如计算、转换等)时,则必须用形参接受参数,也称有参函数。
从Python 3.5版起增加了标准模块在typing,用于支持类型提示(Type Hints)。它提供了:
- 用于类型注释的工具
- 泛型类型支持
- 类型别名
- 回调协议
- 以及其他高级类型系统特性
2. 类型注释
Python 3.5版起导入模块typing模块(Python 3.8版起无需导入typing模块),可以使用“: 数据类型”进行类型注释,有“->数据类型”进行函数类型注释。如:
# 变量类型注释
name: str = "张三"
age: int = 120
is_student: bool = True
# 函数参数和返回值类型注释
def login(name: str, pword: int) -> str:
if pword == 12345678:
return f"欢迎{name}进入系统"
else:
return f" {name},您的密码不正确,不能进入系统"
# 以下用例Python 3.8版后仍需导入typing模块。from typing import List, Dict, Set, Tuple, Optional
# 容器类型
numbers: List[int] = [1, 2, 3]
person: Dict[str, str] = {"name": "张三", "email": "zhangsan@example.biz"}
unique_numbers: Set[int] = {1, 3, 5}
coordinates: Tuple[float, float, float] = (70.5, 95.5, 80.0)
# 可选类型
maybe_name: Optional[str] = None # 等同于 Union[str, None]
例如:定义如图1所示的login()函数之后,当输入“login(”时,系统会给出形参及类型和函数类型提示(如图1所示)。
图1 类型提示(Type Hints)
执行有类型提示(Type Hints)的函数与执行普通函数相同(如图2所示)。
图2 执行有类型提示(Type Hints)的函数
注意:类型提示是可选的,它们不会强制类型检查,但它们可以被工具和IDLE(集成开发学习环境)用来提供更好的代码自动完成、错误检测和文档生成。
二、函数的参数(传参方式)
1. 形参与实参
形式参数(简称形参):函数定义时放在括号中的参数(变量);实际参数(简称实参):函数调用时传入的参数。
2. 默认参数
(1)默认参数的定义使用
函数定义时可以给参数设置默认值,这意味着在调用函数时,如果没有为这些参数传递值,它们将自动使用默认值。这对于编写灵活和可复用的代码非常有用。
例如:在定义函数的形参时用赋值号(=)给参数赋值,定义的就是默认参数。如图3所示的函数greet(),参数greeting默认为“你好”,如调用时不传入greeting,则默认为“你好”,如图3中的greet("张三"),调用时如传入greeting的值,将用传入的值,如图3中的greet("张三", "早上好")和greet("张三", "晚上好"),则greeting为传入的值。
图3 函数的默认参数
(2)默认参数的注意事项
- 默认参数必须是不可变对象:这是因为在函数定义时,默认参数只会被评估一次。如果你使用可变对象(如列表、字典等)作为默认参数,那么所有对该函数的后续调用都将引用相同的对象。这可能会导致意外的副作用。
- 默认参数应该放在参数列表的末尾:必须将带有默认值的参数放在参数列表的末尾,否则将导致语法错误,无法使用默认参数。例如:图4所示的函数def temp(a, b=10, c)会导致语法错误。也就是一旦开始使用默认参数,后面所有参数都得使用默认参数。
图4 函数使用默认参数注意事项
使用默认参数的好处
简化函数调用:用户可以只提供必要的参数,而省略其他具有默认值的参数。
灵活性:允许函数提供更多的配置选项,而不需要创建多个重载的函数。
清晰性:通过明确的参数名和默认值,代码更易于理解和维护。
通过合理使用默认参数,可以编写出既灵活又易于使用的Python函数。
3. 传参方式
Python函数的传参方式可以分为位置参数(Positional Arguments)和关键字参数(Keyword Arguments)两种。理解这两种参数的传递方式对于编写清晰、易于维护的代码非常重要。
(1)位置参数
位置参数是根据函数定义时形式参数的顺序和个数来传递参数。这意味着,当你调用一个函数时,你必须按照函数定义时参数的顺序和个数来传递这些参数。例如:
def greet(name, message):
print(f"你好,{name}:{message}")
# 使用位置参数调用
greet("张三", "多年不见,你身体可好?")
在这个例子中,name 和 message用位置参数传递,它们的值必须按照定义的顺序和个数来传递。
注意:当有默认参数时,默认参数可缺省,默认参数也按顺序赋值或缺省。此时参数传递个数可以少于定义个数。
(2)关键字参数
关键字参数允许在调用函数时通过“参数名=值”的方式来传递参数,这时就不需要按照函数定义时的顺序进行传递,但参数名必须正确。例如:
如图5所示函数,用关键字参数传递,必须使用正确的参数名(关键字),与次序无关。如传递关键字(形参)不存在,会引发TypeError,函数得到一个意外的关键字参数。
图5 关键字参数传递
(3)混用位置参数和关键字参数
在Python中,函数的参数,既可以用位置参数传递,也可以用关键字参数传递,还可以混合使用位置参数和关键字参数传递。但必须位置参数传递在前、关键字参数传递在后。也就是一旦开始使用关键字参数传递,所有后续的参数都必须使用关键字参数形式传递。例如:
def complex_function(a, b, c, d=10, e=20):
print(f"普通参数a: {a},b: {b},c: {c};默认参数d: {d},e: {e}")
# 调用函数,使用位置参数传递
complex_function(1, 2, 3, 4, 5) # 全部参数。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 4,e: 5
complex_function(1, 2, 3, 4) # 缺省默认参数e。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 4,e: 20
complex_function(1, 2, 3) # 缺省默认参数d、e。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 10,e: 20
# 调用函数,使用关键字参数传递
complex_function(e=5, d=4, c=3, b=2, a=1) # 全部参数。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 4,e: 5
complex_function(e=5, c=3, b=2, a=1) # 缺省默认参数d。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 10,e: 5
complex_function(c=3, b=2, a=1) # 缺省默认参数d、e。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 10,e: 20
# 调用函数,混合使用位置参数和关键字参数传递
complex_function(1, 2, c=3, e=5, d=4) # 全部参数。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 4,e: 5
complex_function(1, 2, c=3, e=5) # 缺省默认参数d。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 10,e: 5
complex_function(1, 2, c=3) # 缺省默认参数d、e。结果:普通参数a: 1,b: 2,c: 3;默认参数d: 10,e: 20
注意事项:(1)在调用函数时,如果使用了关键字参数传递,那么必须在所有位置参数之后使用关键字参数。(2)位置参数缺省(有默认参数)只能从右到左逐个进行,直到全部缺省;关键字参数可以缺省任意默认参数。
Python中的输入和输出函数有哪些呢
如何在Python中传递变量?
(4)不定长参数
不定长参数指参数个数不确定的参数,也称动态参数。
- 不定长位置参数:在形参前加“*”号,以元组方式接受任何未被接受的位置参数,参数个数为0~n,如没有位置参数则为空元组。一般用*args表示,args:arguments的缩写,意为参数,表示位置参数。在形参前加“**”号,以字典方式接受任何未被接受的关键字参数,关键字以字符串方式作为字典的“键”,值为字典“键”对应的“值”,参数个数为0~n,如没有关键字参数则为空字典。一般用**kwargs表示,kwargs:keyword arguments的缩写,意为关键字参数。
①只定义*args
def func(*args):
print(args)
func(1,2,'char',[3,4])#输出:(1, 2, 'char', [3, 4])
②只定义**kwargs
def func(**kwargs):
print(kwargs)
func(key1 = 123, key2 = 'char')#输出:{'key1': 123, 'key2': 'char'}。键加引号为字符串
③同时定义*args、**kwargs
def func(*args, **kwargs):
print(args, kwargs)
func(100,200,key1 = 1, key2 = 'char')#输出:(100, 200) {'key1': 1, 'key2': 'char'}
④定义部分形参和*args、**kwargs
def func(a, b, *args, **kwargs):
print(a, b, args, kwargs)
对于④a, b既可以用位置参数调用,也可以用关键字参数调用,除a, b以外的位置参数会以元组方式被args接受,除a, b以外的关键字参数会以字典方式被kwargs接受。
func(100, 200, 300, 400, key1=123, key2='char')#输出:100 200 (300, 400) {'key1': 123, 'key2': 'char'}
func(100, b=200, c=300, d=400, e=123, f='char')#输出:100 200 () {'c': 300, 'd': 400, 'e': 123, 'f': 'char'}。无多余位置参数,所以args为空元组
在④中,需要接受a, b后,多余的位置参数才能被args接受,多余的关键字参数才能被kwargs接受。而a, b既可以用位置参数传递,也可以用关键字参数传递。
⑤位置参数与*args混用案例
def normal_tuple_invoke(x, y=0, *args):
'''
y=0: x的平均成绩=avg(args);y=1: x的最高成绩=max(args)
y=2: x的最低成绩=min(args);y=3: x的总分=sum(args)
其他: x的成绩单: args''' # 函数功能说明
result = ''
if y == 0:
result = x + '的平均成绩=%.2f' % (sum(args)/len(args))
elif y == 1:
result = x + '的最高成绩=' + str(max(args))
elif y == 2:
result = x + '的最低成绩=' + str(min(args))
elif y == 3:
result = x + '的总成绩=' + str(sum(args))
else:
result = x + '的成绩单:' + str(args)
return result
程序中,默认为求平均成绩,输入的成绩按元组接收,可变长度(即成绩多少不限)。
>>> # 按位置+可变长参数(tuple)调用
>>> normal_tuple_invoke('张三',0,67,78,89,90,76,87,95)
'张三的平均成绩=83.14'
>>> normal_tuple_invoke('张三',3,67,78,89,90,76,87,95)
'张三的总成绩=582'
>>> normal_tuple_invoke('张三',4,67,78,89,90,76,87,95)
'张三的成绩单:(67, 78, 89, 90, 76, 87, 95)'
此例只知分数,不知课程。
⑥位置参数与*kwargs混用案例
def keyword_dict_invoke(x, y=0, **kwarg):
'''结果为:
y=0: x的平均成绩=sum(kwarg.values)/len(kwarg.values);
y=1: x的最高成绩=max(kwarg.values);
y=2: x的最低成绩=min(kwarg.values);
y=3: x的总分=sum(kwarg.values)
其它: x的成绩单:kwarg''' # 函数功能说明
result=''
if y==0:
result=x + '的平均成绩=%.2f' % (sum(kwarg.values())/len(kwarg.values()))
elif y==1:
result=x + '的最高成绩=' + str(max(kwarg.values()))
elif y==2:
result=x + '的最低成绩=' + str(min(kwarg.values()))
elif y==3:
result=x + '的总成绩=' + str(sum(kwarg.values()))
else:
result=x + '的成绩单:' + str(kwarg)
return result
while True:
lstC = input('姓名,计算方式,多个"课程名=成绩"对用逗号分隔\n') # 输入时的提示
if lstC == '' or lstC.lower() == 'q': # 退出条件:直接回车或按'Q'
break
strExec="keyword_dict_invoke("+lstC+")" # 生成关键字+可变长参数(dict)调用式
print(eval(strExec)) # 执行函数keyword_dict_invoke()
执行结果如下:
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
'张三',0,政治经济学=67,生涯规划=78,Python程序设计=89,金属工艺学=90
张三的平均成绩=81.00
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
'张三',1,政治经济学=67,生涯规划=78,Python程序设计=89,金属工艺学=90
张三的最高成绩=90
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
'张三',2,政治经济学=67,生涯规划=78,Python程序设计=89,金属工艺学=90
张三的最低成绩=67
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
'张三',3,政治经济学=67,生涯规划=78,Python程序设计=89,金属工艺学=90
张三的总成绩=324
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
'张三',4,政治经济学=67,生涯规划=78,Python程序设计=89,金属工艺学=90
张三的成绩单:{'政治经济学': 67, '生涯规划': 78, 'Python程序设计': 89, '金属工艺学': 90}
姓名,计算方式,多个"课程名=成绩"对用逗号分隔
此例既知分数,又知课程。
(5)强制只接受位置参数和只接受关键字参数
Python3.8及之后在函数定义语法中引入了两个特殊参数(Special parameters)/和*,可强制分开函数的位置参数和关键字参数。参数/之前的参数为仅限位置参数(positional-only arguments),只接受位置参数,参数/之后的参数则既可接受位置参数,也可以接受关键字参数;参数*之后的参数为仅限关键字参数(keyword-only arguments),只接受关键字参数,参数/之前的参数则既可接受位置参数,也可以接受关键字参数。
至于/和*之间的参数,它们遵循默认的行为,既可以通过位置传入,也可以通过关键字传入。当然,在函数的定义中,既可以只用一个,也可以都不用,或者两个都用。
①强制位置参数
所有/之前的参数都是仅限位置参数(positional-only arguments),这意味着它们只能作为位置参数传递给函数,而不能作为关键字参数传递。
一般,核心参数,或在参数顺序比参数名称更重要的情况下,或者参数的名字本身没什么语义(开发者希望在未来可以随时修改这个参数的名字),例如在处理图像或执行几何计算时,此功能非常有用。通过使用仅限位置参数,仅需确保以正确的顺序传参调用函数,从而提高代码的清晰度和可维护性。
def positional_only(a, b, c, /): # 已知三边长求三角形面积
s = (a + b + c) / 2 # 计算半周长
# 使用海伦公式计算面积
area = (s * (s - a) * (s - b) * (s - c)) ** 0.5
return area
对于上面这个函数而言,调用positional_only函数时,参数a、b、c只能是位置参数,执行
positional_only(3,4,5)
6.0
正确。执行
positional_only(3,4,c=5)
Traceback (most recent call last):
File "<pyshell#82>", line 1, in <module>
positional_only(3,4,c=5)
TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'c'抛出错误。
图6 仅限位置参数用例
②强制关键字参数
所有*之后的参数都是仅限关键字参数(keyword-only arguments),它们只能作为关键字参数传递给函数,不能作为位置参数传递。
当您想要强制对某些参数(例如具有默认值的可选参数)使用关键字参数时,或者当您想要明确区分必需参数和可选参数时,又或者这些参数在未来的位置也随时可能发生变化(在前面加入新的参数之类),此功能非常有用。
def keyword_only(*, x, y, width, height):
""" 根据左上角坐标、宽和高绘制矩形
参数:
x: 左上角x坐标
y: 左上角y坐标
width: 矩形宽度
height: 矩形高度
"""
tl.penup() # 抬笔
tl.goto(x, y) # 移动到左上角起点
tl.pendown() # 落笔
# 绘制矩形(顺时针方向)
for i in range(2):
tl.forward(width) # 向右画宽度
tl.right(90) # 右转90度
tl.forward(height) # 向下画高度
tl.right(90) # 右转90度
tl.done() # 保持绘图窗口打开
对于上面这个函数而言,调用keyword_only函数时,强制参数x,y,width,height只能是关键字参数(keyword-only arguments),这四个参数不能用位置参数传参。
图7 仅限关键字参数用例
③/和*都出现在函数参数中
以下哪个函数调用是合法的?( )
def func(a, /, b, *, c, d=0):
pass
A. func(1, 2, 3, 4) B. func(a=1, 2, c=3) C. func(1, 2, 3, d=4) D. func(1, 2, c=3)
从上例函数定义可知,强制a是仅限位置参数,b(不做限制)是既可位置参数也可关键字参数,强制c、d为仅限关键字参数,且d为默认参数。
解析:答案D。选项A:c、d用位置参数调用仅限凑字参数,错误。选项B:a用关键字参数调用仅限位置参数,错误。选项C:c用位置参数调用仅限关键字参数,错误。选项D:a、b用位置参数调用,c用关键字参数调用,d为默认参数,可以缺省,正确。故选D。
图8 同时使用仅限位置参数、仅限关键字参数用例
分析以下函数应该如何调用?
def func(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
解析:参数a、b为仅限位置参数,必须强制用位置参数调用;参数e、f为仅限关键字参数调用,参数c、d则既可以用位置参数调用,也可以用关键字参数调用。所以:
func(1, 2, 3, 4, e=5, f=6) # 正确调用
参数a、b用位置参数调用,参数c、d用位置参数调用,参数e、f用关键字参数调用,可以正常调用。
func(1, b=2, c=3, d=4, e=5, f=6) # 这会引发TypeError,因为b不能作为关键字参数传递
参数b为仅限位置参数,用关键字参数调用,抛出TypeError,不能正确调用。