我们的gzh是【朝阳三只大明白】,满满全是干货,分享近期的学习知识以及个人总结(包括读研和IT),希望大家一起努力,一起加油!求关注!!
概述
函数是组织好的、可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。
Python提供了许多内建函数,比如print()
、sum
,但是有些时候基础函数并不能实现想要的业务功能,所以必须要自定义函数,函数的使用必须遵循“先定义,后调用”的原则。函数的定义就相当于事先将内存地址赋值给定义的函数名,函数名就是对这段代码的引用,这类似于变量的定义,在Python中一个函数其实是一个【function】对象或者【method】对象(取决于函数是隶属于一个类还是单独存在)。
def add(a: int, b: int) -> int:
"""
return a+b
:param a: 参数1,整形,加数1
:param b: 参数2,整形,加数2
:return: a+b
"""
return a + b
上面就是Python函数的一个例子,这段代码主要包括:
- 【def】关键字;
- 函数名称;
- 函数文档描述;
- 函数参数和注解;
- 函数体;
- 函数返回值和注解;
函数参数
函数的参数分为形式参数和实际参数,简称形参和实参。
- 形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名、一个引用,用来接收外部传来的值;
- 实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合。
在Python中,变量名与值只是单纯的绑定关系,而对于函数来说,这种绑定关系只在函数调用时生效,在调用结束后解除。
位置参数
位置参数也称必须参数,位置参数须以从左到右的顺序传入函数。调用时的数量必须和声明时的一样。
def add(a: int, b: int) -> int:
"""
return a+b
:param a: 参数1,整形,加数1
:param b: 参数2,整形,加数2
:return: a+b
"""
return a + b
add(1, 2)
在上面那个例子汇总,【a】和【b】就是位置参数,add(1, 2)
就是将a和1绑定,b和2绑定。
关键字参数
在调用函数时,实参可以是key=value
的形式,称为关键字参数。凡是按照这种形式定义的实参,可以不按照从左到右的顺序定义,但仍能进行形参和实参的绑定。使用该方式传递(绑定)参数的时候有两点需要注意:
- 不可以对一个形参重复赋值;
- 关键字参数在位置参数后面,因为python函数在解析参数时,是按照顺序来的,位置参数是必须先满足,才考虑其他可变参数。
def add(a: int, b: int) -> int:
"""
return a+b
:param a: 参数1,整形,加数1
:param b: 参数2,整形,加数2
:return: a+b
"""
return a + b
add(1, 2) -> 3
add(a=1, b=2) -> 3
add(b=2, a=1) -> 3
add(1, b=2) -> 3
add(2, a=1) -> add() got multiple values for argument 'a'
add(a=1, 2) -> positional argument follows keyword argument
默认参数
在定义函数时,就已经为形参赋值,这类形参称之为默认参数(缺省参数)。当一个函数拥有多个参数且函数不可重构的时候默认参数将会十分有效,可以将值经常改变的参数定义成位置参数,将值改变较少的参数定义成默认参数,从而降低函数调用的复杂度。
def add(a: int, b: int=2) -> int:
"""
return a+b
:param a: 参数1,整形,加数1
:param b: 参数2,整形,加数2
:return: a+b
"""
return a + b
add(1) -> 3
add(1, 3) -> 4
在上面那个例子中,【b】就是一个默认参数,使用默认参数有几点注意事项:
- 默认参数必须在位置参数之后;
- 默认参数的值仅在函数定义阶段被赋值一次;
- 默认参数的值通常应设为不可变类型。
动态参数
顾名思义,动态参数就是传入的参数的个数是动态的,可以是1个、2个到任意个,还可以是0个。在不需要的时候,你完全可以忽略动态函数,不用给它传递任何值。
在Python中,动态参数有两种分别是*args
和**kwargs
,二者的区别在于星号的数量,前者会被处理成一个元祖,而后者会被处理成一个字典。
def func_tuple(*args: Tuple[Any]) -> None:
print(args)
def func_dict(**kwargs: Mapping[Any, Any]) -> None:
print(kwargs)
my_dict = {1:'a', 2:'b', 3:'c'}
my_str_dict = {'1':'a', '2':1, "3":3.5, '4': [1, 2, 3]}
my_tuple = (1, 2, 3, 4)
func_tuple(1, 'a', 3.5, [1, 2, 3]) -> (1, 'a', 3.5, [1, 2, 3])
func_tuple(*my_tuple) -> (1, 2, 3, 4)
func_dict(my_dict) -> func_dict() takes 0 positional arguments but 1 was given
func_dict(**my_dict) -> func_dict() keywords must be strings
func_dict(**my_str_dict) -> {'1': 'a', '2': 1, '3': 3.5, '4': [1, 2, 3]}
func_dict(a=1, b=2, c=3) -> {'a': 1, 'b': 2, 'c': 3}
func_dict(my_dict)
报错的原因在于解析器将【my_dict】当做一个位置参数进行传递,而函数本身没有位置参数,所以报错了,正确的做法是使用**
对字典进行解包或者实参从左到右都是关键字参数的形式传递(字典解包还需要注意他的键必须是字符类型)。如果想要对元祖解包,需要使用*
号。
限定位置形参
也称强制位置参数,在Python3.8之后被引入,被定义为限定位置形参的参数必须放在形参表的最前面,并在后面使用斜杠【/】(独占一个参数位)与普通形参分隔,比如下面这样:
def func(a, b, /, c, d) -> None:
print(a + b + c + d)
func(1, 2, 3, 4) -> 10
func(1, b=2, c=3, d=4) -> TypeError: func() got some positional-only arguments passed as keyword arguments: 'b'
其中【a】、【b】是限定位置形参,只能按照位置参数的形式进行传递,而【c】和【d】仍然是普通形参,可以接受位置参数和关键字参数两种形式传递。
限定关键字形参
也称命名关键字参数,和限定位置形参的目的一样,就是为了限定参数只能以关键字参数的形式进行传递,使用它的好处在于可以显式地指明参数含义,有利于可读性,同时强制使用关键字参数的形式也有利于保证版本兼容。被定义为限定关键字形参的参数需要放在形参表的后面,并在前面使用星号【*】(独占一个参数位)与普通形参分隔,示例如下:
def func(a, b, *, c, d) -> None:
print(a + b + c + d)
func(1, 2, c=3, d=4) -> 10
func(1, 2, 3, 4) -> TypeError: func() takes 2 positional arguments but 4 were given
其中【c】和【d】是限定关键字形参,【a】、【b】仍然是普通形参。
综合示例
为了进一步了解参数的各个类型,我们可以使用inspect.signature
来获取函数对象(function)的相关属性和定义,示例如下:
def show_func_detail(sig):
for name, param in sig.parameters.items():
print('参数类型:%s, 参数名:%s, 参数默认值:%s' % (param.kind, name, param.default))
def func1(a, b, c, d):
pass
def func2(a, b, c=3, d='default'):
pass
def func3(*args):
pass
def func4(**kwargs):
pass
def func5(a, b, /, c, d):
pass
def func6(a, b, *, c, d):
pass
sig1 = signature(func1)
sig2 = signature(func2)
sig3 = signature(func3)
sig4 = signature(func4)
sig5 = signature(func5)
sig6 = signature(func6)
show_func_detail(sig1)
show_func_detail(sig2)
show_func_detail(sig3)
show_func_detail(sig4)
show_func_detail(sig5)
show_func_detail(sig6)
----------------------------------------------------------------------------
参数类型:POSITIONAL_OR_KEYWORD, 参数名:a, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:b, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:c, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:d, 参数默认值:<class 'inspect._empty'>
----------------------------------------------------------------------------
参数类型:POSITIONAL_OR_KEYWORD, 参数名:a, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:b, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:c, 参数默认值:3
参数类型:POSITIONAL_OR_KEYWORD, 参数名:d, 参数默认值:default
----------------------------------------------------------------------------
参数类型:VAR_POSITIONAL, 参数名:args, 参数默认值:<class 'inspect._empty'>
----------------------------------------------------------------------------
参数类型:VAR_KEYWORD, 参数名:kwargs, 参数默认值:<class 'inspect._empty'>
----------------------------------------------------------------------------
参数类型:POSITIONAL_ONLY, 参数名:a, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_ONLY, 参数名:b, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:c, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:d, 参数默认值:<class 'inspect._empty'>
----------------------------------------------------------------------------
参数类型:POSITIONAL_OR_KEYWORD, 参数名:a, 参数默认值:<class 'inspect._empty'>
参数类型:POSITIONAL_OR_KEYWORD, 参数名:b, 参数默认值:<class 'inspect._empty'>
参数类型:KEYWORD_ONLY, 参数名:c, 参数默认值:<class 'inspect._empty'>
参数类型:KEYWORD_ONLY, 参数名:d, 参数默认值:<class 'inspect._empty'>
从上面的示例可以看到:
- 普通形参:POSITIONAL_OR_KEYWORD;
- 默认参数只是更改了参数的默认值,不更改它的类型;
- 动态参数:VAR_POSITIONAL或者VAR_KEYWORD;
- 限定位置形参:POSITIONAL_ONLY;
- 限定关键字形参:KEYWORD_ONLY。
总结
- 函数形参只是绑定关系;
- 普通的函数形参可以接受两种形式(位置参数形式和关键字参数形式),每个都有自己的注意事项;
- 如果函数接受多个参数,可以给函数赋予默认值(最好是不可变类型);
- 如果不确定传入参数的数量使用动态参数;
- 合理的使用限定位置形参和限定关键字形参可以有效提升代码的可读性;
中难免会出现一些描述不当之处(尽管我已反复检查多次),欢迎在留言区指正,相关的知识点也可进行分享。