Python/函数
函数定义
[编辑]Python函数定义的通常格式为:
def 函数名(参数列表):
#函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明
函数体
#return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None
参数的传值与传引用
[编辑]Python中,对象有类型;变量仅仅是对象的一个引用,变量没有类型。 string、tuple和 numbers 是不可更改的类型(immutable type),给它们的变量赋值其实是指向了新的对象;而 list,dict 等则是可以修改的类型(mutable type)。Python函数的参数传递,在函数内部最初都是访问实参那个对象而不是复制一份:
- 不可变类型:在函数内部“修改”参数的值,只是引用到另一个对象,不影响实参本身。
- 可变类型:在函数内部修改参数的值后,函数外部的实参也会受影响
命名实参与不定长参数
[编辑]Python的函数实参可分为两类:
- 无命名的非关键字实参(non-keyword argument)或位置实参(positional argument)
- 关键字实参(keyword argument)或译作命名实参。关键字实参之间的顺序可以任意。
关键字实参必须出现在非关键字实参右侧,否则报错“positional argument follows keyword argument”。
函数形参可分为
- 无缺省值的形参
- 有缺省值的形参
*args
**kwargs
,称为“var-keyword argument”。*
(单个星号)/
(单个斜杠)
def foo(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- | | | | Positional or keyword | | - Keyword only -- Positional only
def bar(param1, param2==default_value,*args, kwd1, **kwargs):
函数的形参表必须遵守下属规则:
- 出现在
*args
或者**kwargs
或者*
之后的形参都只能对应命名实参 - 带缺省值的形参,其右侧直至*的参数都必须是有缺省值形参。参数的缺省值在函数定义时从左至右求值。
- 形参中没有名字的单独一个星号,只是起到分隔符作用,并不对应于实参,表示实参表此处向右都必须是命名实参。这适用于当名称有意义并且函数定义通过显式名称更容易理解时,只用关键字实参;或者您希望防止用户依赖所传递参数的位置。
- 形参中没有名字的单独一个斜杠,只是起到分隔符作用,并不对应于实参,表示实参表此处向左都必须是位置形参。这适用于调用函数时强制实参的求值顺序;或者以后修改形参名字但不影响调用者使用。“仅是位置形参”(Positional only)的名字将不再尝试与关键字实参的名字匹配,这意味着同名的关键字实参可以被**kwargs收集,这称为Recap。
- 形参**kwargs必须出现在*args的右侧,否则报错“invalid syntax”
- 形参**kwargs必须是最后一个形参,否则报错“arguments cannot follow var-keyword argument”
- 通常,不在一个函数同时使用*args与**kwargs,以免混淆。
函数调用时,形实参数结合的规则:
- 首先实参表左侧的所有非关键字实参依次与形参匹配。
- 已经匹配的非关键字形参,其对应的实参的名字不能再作为关键字实参出现,否则报错“duplicate value for the same argument”或“got multiple values for argument”。
- 未匹配的非关键字实参组成一个元组与*args形参匹配。
- 未匹配的关键字实参组成一个字典与形参**kwargs形参匹配。
- 如果没有**kwargs形参,关键字实参的名字不出现在形参表中则报错“an unexpected keyword argument”
- 不能被匹配的形参,将报错“missing 1 required positional argument”或“missing 1 required keyword-only argument”
函数调用时,如何传入任意多的实参? 使用“逆向参数收集”。即在list变量名字前面加上*号作为实参;tuple变量名字前面加上**号作为实参。
def test1(a, b, c=0, *args, **kwargs):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kwargs)
def test2(a, b, c=0, *args, d, **kwargs):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'args=', args, 'kw =', kwargs)
# 定义一个元组和字典用作参数传入
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
test1(*args, **kw)
# a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
test2(*args, **kw)
#a = 1 b = 2 c = 3 d = 99 args= (4,) kw = {'x': '#'}
def test3(a, *args,d, c=0):
print('a =', a, 'c =', c, 'd =', d, 'args=', args)
args=(1,3,5)
test3(-1, d=101, *args)
#输出为:a = -1 c = 0 d = 101 args= (1, 3, 5)
函数返回多个值
[编辑]返回多个值时自动作为一个tuple。函数调用者可以使用多个变量来接受多个返回值(即tuple中的各个成分)。
嵌套函数
[编辑]嵌套函数(nested function),需要注意:
- 内部函数可以读写访问外部函数的可变(mutable)变量
- 内部函数可以读访问外部函数的不可变(immutable)变量
- 内部函数使用nonlocal关键字,可以读写访问直接外部函数的不可变(immutable)变量
- 内部函数访问的外部函数的变量,可以定义在内部函数之后。
函数闭包
[编辑]闭包是指嵌套函数作为外部函数的返回值。因为函数是头等对象,所以函数对象可以作为实参、返回值。嵌套函数可以使用外部函数的变量,即使外部函数已经运行结束了。例如
def adder(outer_argument): # outer function
def adder_inner(inner_argument): # inner function, nested function
return outer_argument + inner_argument # Notice outer_argument
return adder_inner
add5 = adder(5) # a function that adds 5 to its argument
add7 = adder(7) # a function that adds 7 to its argument
print add5(3) # prints 8
print add7(3) # prints 10
匿名函数
[编辑]匿名函数的语法如下:
lambda 可选的形参表:expression
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。lambda 函数拥有自己的命名空间,可以访问包含它的作用域中的变量。
def make_incrementor(n): return lambda x: x + n
偏函数
[编辑]对一个函数的部分参数绑定值后,得到的函数为偏函数:
偏函数名 = partial(func, *args, **kwargs)
其中,func 指的是要封装的原函数,*args 和 **kwargs 分别用于接收非命名实参和命名实参。
from functools import partial
#定义个原函数
def display(name,age):
print("name:",name,"age:",age)
#定义偏函数,其封装了 display() 函数,并为 name 参数设置了默认参数
GaryFun = partial(display,name = 'Gary')
#由于 name 参数已经有默认值,因此调用偏函数时,可以不指定
GaryFun(age = 13) #必须以命名实参方式传入,否则会报错:TypeError: display() got multiple values for argument 'name'
def mod( n, m ):
return n % m
#定义偏函数,并设置参数 n 对应的实参值为 100
mod_by_100 = partial( mod, 100 )
print(mod( 100, 7 ))
print(mod_by_100( 7 ))
eval()与exec()
[编辑]eval(source, globals=None, locals=None, /) exec(source, globals=None, locals=None, /)
expression参数是一个字符串,代表要执行的语句 。该语句受后面两个字典类型参数 globals 和 locals 的限制,只有在 globals 字典和 locals 字典作用域内的函数和变量才能被执行。
globals参数管控的是一个全局的命名空间,即 expression 可以使用全局命名空间中的函数。如果只是提供了 globals 参数,而没有提供自定义的 __builtins__,则系统会将当前环境中的 __builtins__ 复制到自己提供的 globals 中,然后才会进行计算;如果连 globals 这个参数都没有被提供,则使用 Python 的全局命名空间。
locals参数管控的是一个局部的命名空间,和 globals 类似,当它和 globals 中有重复或冲突时,以 locals 的为准。如果 locals 没有被提供,则默认为 globals。
eval() 执行完会返回结果,而 exec() 执行完不返回结果
map()、filter()、reduce()
[编辑]由于 map() 函数是直接由用 C 语言写的,运行时不需要通过 Python 解释器间接调用,并且内部做了诸多优化,所以相比其他方法,此方法的运行效率最高。
reduce() 函数在 Python 3.x 中已经被移除,放入了 functools 模块,因此在使用该函数之前,需先导入 functools 模块。
函数注解
[编辑]“函数注解是关于用户自定义函数使用类型的可选的元信息”。也就是说,函数注解的用途为“函数中的形参和返回值提供类型提示信息”。注意,函数注解没有任何语法上的意义,只是为函数参数和返回值做注解,并在运行获取这些注解,仅此而已。换句话说,为函数做的注解,Python不做检查,不做强制,不做验证,什么操作都不做,函数注解对Python解释器没任何意义。
def f(ham:str,egg:str='eggs')->str:
pass
print(f.__annotations__)
#输出结果为:{'ham': <class 'str'>, 'egg': <class 'str'>, 'return': <class 'str'>}