跳转到内容

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或者*之后的形参都只能对应命名实参
  • 带缺省值的形参,其右侧一直到*的所有参数都必须是有缺省值形参。参数的缺省值在函数定义时从左至右求值。
  • 形参中没有名字的单独一个星号*,只是起到分隔符作用,并不对应于实参,表示实参表此处向右都必须是命名实参。这适用于当名称有意义并且函数定义通过显式名称更容易理解时,只用关键字实参;或者您希望防止用户依赖所传递参数的位置。
  • 从Python 3.8开始,形参中没有名字的单独一个斜杠/,只是起到分隔符作用,并不对应于实参,表示实参表此处向左都必须是位置形参。这适用于调用函数时强制实参的求值顺序;或者以后修改形参名字但不影响调用者使用。“仅是位置形参”(Positional only)的名字将不再尝试与关键字实参的名字匹配,这意味着同名的关键字实参可以被**kwargs收集,这称为Recap。
  • 形参**kwargs必须出现在*args的右侧,否则报错“invalid syntax”
  • 形参**kwargs必须是最后一个形参,否则报错“arguments cannot follow var-keyword argument”
  • 通常,不在一个函数同时使用*args**kwargs,以免混淆。

调用函数时,实参可分为四类:

  1. 位置实参
  2. 关键字实参,形如 name=value
  3. 单星号解包实参,形如*args
  4. 双星号解包实参,形如**kwargs

实参在实参表中出现的规则:

  1. 实参表可分为左、中、右三部分,任何部分都可以为空
  2. 实参表左部分,任意数量的位置实参可以和任意数量的单星号解包实参混杂出现;
  3. 实参表中部分,任意数量的关键字实参可以和任意数量的单星号解包实参混杂出现;
  4. 实参表右部分,任意数量的关键字实参可以和任意数量的双星号解包实参混杂出现。

函数调用时,形实参数结合的规则:

  1. 首先形参表的“仅位置形参”与位置实参从左向右结合。
  2. 其次形参表的“仅关键字形参”与关键字实参实参结合。
  3. 再次对于“可位置可关键字形参”:
    1. 首先与关键字实参匹配。
    2. 其次对于不能匹配关键字实参的“可位置可关键字形参”,再与位置实参匹配。
    3. 最后还剩下未匹配的位置实参组成一个元组收集到*args形参,还剩下未匹配的关键字实参组成一个字典收集到**kwargs形参。

形参实参结合时一些语法报错情形:

  • 已经匹配的非关键字形参,其对应的实参的名字不能再作为关键字实参出现,否则报错“duplicate value for the same argument”或“got multiple values for argument”。
    • 特例:“仅是位置形参”(Positional only)的名字将不再尝试与关键字实参的名字匹配,这意味着同名的关键字实参可以被**kwargs收集,这称为Recap。
  • 如果没有**kwargs形参,关键字实参的名字不出现在形参表中则报错“an unexpected keyword argument”
  • 不能被匹配的形参,将报错“missing 1 required positional argument”或“missing 1 required keyword-only argument”
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)变量(如 list、dict 等)
  • 内部函数可以访问外部函数的不可变(immutable)变量(如 int、str、tuple 等)
  • 内部函数使用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'>}