简介
是对程序逻辑进行结构化或过程化的一种编程方法。将大量重复代码放到函数中,即可以节省空间,又有助于保持一致性;
函数关键字def定义,def关键字后跟一个函数的标识符名称,然后跟一对圆括号。圆括号之中可以包含一些变量名,该行以冒号结尾。接下来就是函数体;
函数在执行时,使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,首先从局部符号表中查找,然后从外层函数局部符号表中查找,再从全局符号表,最后是内置符号表。因此,尽管可以引用全局变量和外层函数变量,但是最好不要在函数内直接赋值,除非是global语句定义的全局变量,或nonlocal语句定义的外层函数变量
在调用函数时,会将实际参数引入到被调用函数的局部符号表中;因此,实参是使用按值调用来传递的;当一个函数调用另一个函数时,会为该调用创建一个新的全局符号表;
函数定义在当前符号表中把函数名与函数对象关联在一起,解释器把函数名指向的对象作为用户自定义函数。还可以使用其他名称指向一个函数对象,并访问该函数;
参数
参数在函数定义的圆括号中指定,以逗号分隔;函数中的参数名称为形参,调用函数时传入的参数为实参;
默认值参数
调用函数时,可以使用比定义时更少的参数;极大的提升了程序的健壮性和灵活性;
def f(arg1, arg2='2'):
pass
默认值只计算一次。会在执行函数定义时按从左至右的顺序被求值;当为列表、字典或类实例等可变对象时,会产生与该规则不同的结果;
如:
def f(a, l=[]):
l.append(a)
return l
print(f(1)) # [1]
print(f(2)) # [1, 2]
print(f(3)) # [1, 2, 3]
关键字参数
key/value形式的参数;在函数调用时,关键字参数必须跟在位置参数后面。所有传递的关键字参数都必须匹配一个函数接收的参数;关键字参数的顺序并不重要;如果最后一位形参为**kw形式,该字典包含除了函数中已经定义的形参之外的所有关键字参数;
可以通过**dct形式,接收一个字典,该字典包含与函数中已定义形参对应之外的所有关键字参数;
特殊参数
参数可以按照位置或关键字传递给python函数;为了让代码易读、高效,最好限制参数的传递方式,这样让开发者查看函数定义,即可确定参数项是仅按位置、按位置或关键字、仅限关键字传递;可以通过 / 或 * 来定义
如:
def f(pos1, pos2, /, pos_or_kw, *, k1, k2):
------------ --------- -------
| | |
仅按位置参数 按位置或关键字 仅按关键字参数
pass
/ 和 * 是可选的;这些符号表明形参如何把参数值传递给函数;仅限位置、按位置或关键字、仅限关键字;
仅限位置参数
仅限位置参数时,形参的顺序很重要,且这些形参不能使用关键字传递。仅限位置形参必须放在 / 之前;/ 在逻辑上分隔仅限位置形参;如果函数定义中没有 / ,则表示没有仅限位置参数; / 后可以是位置或关键字或仅限关键字形参;
仅限关键字参数
形参标记为仅限关键字,则表明必须以关键字参数形式传递形参,应该在参数列表中第一个仅限关键字形参前添加 * ;
def standard_arg(arg):
pass
def pos_only_arg(arg, /):
pass
def kwd_only_arg(*, arg):
pass
def combined_example(pos_only, /, standard, *, kwd_only):
pass
第一个函数:是最常见的形式;对于调用方式没有任何限制,可以按位置也可以按照关键字传递
>>> standard_arg(2)
>>> standard_arg(arg=2)
第二个函数:定义中存在 /,那么仅限位置参数
>>> pos_only_arg(1)
第三个函数:定义中存在 * ,表明仅限关键字参数
>>> kwd_only_arg(arg=1)
第四个函数:使用全部三种惯例
>>> combined_example(1, 2, kwd_only=3)
任意实参列表
调用函数时,使用任意数量的实参是最少见的选项;这些实参包含在元组中;
*args用于接收传递给函数的所有剩余位置参数;因此*args之后不允许出现位置参数;
def f(file, *args):
pass
解包实参列表
函数调用要求独立的位置参数,但实参在列表或元组里时,需要通过 * 操作符把实参从列表或元组中解包处理啊;
同样也可以通过 ** 操作符把实参从字典中解包;
list(range(*[2, 6]))
f(**{'k1': v1, 'k2': v2})
lambda
lambda用于创建匿名函数。也可用于任何需要函数对象的地方。在语法上,匿名函数只能是单个表达式。在语义上,它只是常规函数定义的语法糖。与嵌套函数一样,lambda也可以引用包含作用于中的变量;
lambda x, b: a + b
函数注解
函数注解是可选的;只是一种在编译阶段将python表达式与函数的各个部分建立关联的方式,python本身不会为注解关联任何特定的含义或意义;注解表达式是独立存在的,python只是让这些表达式可供使用,仅当第三方库解释时,注解才能发挥作用;
参数注解
格式:
def f(a: expression, b: expression = 5):
pass
参数注解始终位于参数默认值之前,注解和默认值都是可选的。用冒号把注解标出来。在函数定义得以运行时,将会对所有注解表达式进行求值;
*args和**kwargs可变长参数的注解
def f(*args: expression, **kwargs: expression)
嵌套参数的注解:
总跟在参数名的后面
def f((x1, y1: expression), (x2: expression, y2: expression) = (None, None)):
pass
返回值注解
格式:
def f() -> expression:
pass
参数列表后面跟一个 -> 和python表达式。和参数注解一样,函数定义得以运行时将会对该表达式求值;
lambda
不支持注解。当然也可以改为支持注解的格式,需要在参数列表两侧加上括号;
有以下原因,决定不再改动:
- 该改动不具有兼容性
- lambda已经式微
- lambda表达式一定能变成函数
函数注解的读取
只要经过编译,函数注解就可以通过函数的__annotations__属性访问;该属性是一个可变的字典,将参数名称映射一个代表已求值注解表达式的对象;
在__annotations__映射中有一个特殊键return,仅当提供了函数返回值的注解时才有效;选择return是因为为了避免参数名称发生冲突,任何时候把return作为参数名称,都会引发SyntaxError
如果函数没有注解或由lambda表达式生成的,则__annotations__将是一个空字典;
如:
def f(a: 'x', b: 5+6, c: list) -> max(2, 9):
pass
# __annotations__映射为:
{'a': 'x', 'b': 11, 'c': list, 'return': 9}
文档字符串
第一行应该为函数用途的简短摘要。为了保持简洁,不要在这里显示说明对象名或类型,因为可通过其它方式获取这些信息;这一行应该为大写字母开头,以句点结尾
文档字符串为多行时,第二行应为空白行,在视觉上将摘要与其描述分开。后面的行可包含若干段落,描述对象的调用约定、副作用等等;
python解析器不会删除python中多行字符串字面值的缩紧,因此,文档处理工具应在必要时删除缩进。
def f():
"""Do nothing, but document it.
No, really, it doesn't do anything.
"""
pass
return
return跳出函数。如果函数没有返回值return语句,那么等价于return None
嵌套函数
内部函数整个函数题都在外部函数作用于,如果没有任何对内部函数的引用,那么,1除了在函数体内,任何地方都不能对其进行调用;
如果内部函数的定义包含了外部函数里面定义的对象引用,内部函数就会变成闭包