深入解析Python中函数的参数与作用域
传递参数 函数传递参数时的一些简要的关键点:
实际上,Python的参数传递模型和C语言的相当相似: 不可变参数”通过值”进行传递。像整数和字符串这样的对象是通过对象引用而不是拷贝进行的,但是因为不论怎么样都不可能在原处改变不可变对象,实际的效果就很像创建了一份拷贝。 传递参数时,传递一个拷贝: L = [1,2] changer(L[:]) 函数内部进行拷贝 def changer(b): b=b[:] 将可变对象转化为不可变对象 L=[1,2] changer(tuple(L)) >>对参数输出进行模拟 def multiple(x,y): x = 2 y = [2,4] return x,y #Return new values in a tuple 这段代码貌似返回了两个值,其实只有一个:一个包含了2个元素的元组,它的括号是可以省略的。 特定的参数匹配模型 >>基础知识
>>匹配语法
相应的说明: 在函数的调用中(表中的前4行),简单的通过变量名位置进行匹配,但是使用name=value的形式告诉Python依照变量名进行匹配,这些叫做关键字参数。在调用中使用*sequence或**dict允许我们在一个序列或字典中相应地封装任意多的位置相关或者关键字的对象,并且在将他们传递给函数的时候,将它们解包为分开的、单个的参数。 在函数调用中,参数必须以此顺序出现:任何位置参数(value),后面跟着任何关键字参数(name=value)和*sequence形式的组合,后面跟着**dict形式。 Python内部是使用以下的步骤来在赋值前进行参数匹配的:
>>关键字参数和默认参数的实例 def f(a,b,c): print(a,c) f(1,2,3) #Prints 1,3 关键字参数 关键字参数允许通过变量名进行匹配,而不是通过位置。 f(c=3,b=2,a=1) #Prints 1,3 默认参数 默认参数允许创建函数可选的参数。如果没有传入值的话,在函数运行前,参数就被赋了默认值。 def f(a,c=3): print(a,c) f(1) #Prints 1,3 f(1,4) #Prints 1,4,c=6) #Prints 1,6 关键字参数和默认参数的混合 def func(spam,eggs,totast=0,ham=0): print((spam,ham=0)) func(1,2) #Ouput:(1,0) func(1,ham=1,eggs=0) #Ouput:(1,1) func(spam=1,eggs=0) #Ouput:(1,0) func(toast=1,eggs=2,spam=3) #Ouput:(3,1,3,4) #Ouput:(1,4) >>任意参数的实例 收集参数 在函数定义中,在元组中收集不匹配的位置参数。 def f(*args):print(args) 当这个函数调用时,Python将所有位置相关的参数收集到一个新的元组中,并将这个元组赋值给变量args。因此它是一个一般的元组对象,能够进行索引或迭代。 **特性类似,但是它只对关键字参数有效。将这些关键字参数传递给一个新的字典,这个字典之后将能够通过一般的字典工具进行处理。在这种情况下,**允许将关键字参数转化为字典,你能够在之后使用键调用进行步进或字典迭代。 def f(a,*pargs,**kargs):print(a,pargs,kargs) f(1,x=1,y=2) #Prints:1 (2,3) {'x':2,'y':1} 解包参数 在最新的Python版本中,我们在调用函数时能够使用*语法。在这种情况下,它与函数定义的意思相反。它会解包参数的集合,而不是创建参数的集合。 def func(a,c,d):print(a,d) args=(1,2) args+=(3,4) func(*args) #Prints 1,4 相似的,在函数调用时,**会以键/值对的形式解包一个字典,使其成为独立的关键字参数。 args={'a':1,'b':2,'c':3} args['d']=4 func(**args) #Prints 1,4 注意:别混淆函数头部和函数调用时*/**的语法:在头部,它意味着收集任意多的参数,而在调用时,它解包任意数量的参数。 应用函数通用性 if <test>: action,args=func1,(1,) else: action,args=func2,3) ... action(*args) >>Python3.0 Keyword-Only参数 从语法上讲,keyword-only参数编码为命名的参数,出现在参数列表中的*args之后。所有这些参数都必须在调用中使用关键字语法来传递。 我们也可以在参数列表中使用一个*字符,来表示一个函数不会接受一个变量长度的参数列表,而是仍然期待跟在*后面的所有参数都作为关键字传递。 def kwonly(a,*,c) kwonly(1,c=3,b=2) #Prints:1,3 kwonly(c=3,a=1) #Prints:1,3 kwonly(1,3) #Error! 上述代码中,b和c必须按照关键字传递,不允许其他额外的位置传递。 另外,默认函数仍然对keyword-only参数有效,所以,实际上,带有默认值的keyword-only参数都是可选的,但是,那些没有默认值的keyword-only参数真正地变成了函数必需的keyword-only参数。 排序规则 最后,注意keyword-only参数必须在一个单个星号后指定,而不是两个星号――命名的参数不能出现在**args任意关键字形式的后面,并且一个**不能独自出现在参数列表中。这两种做法将产生错误。 def kwonly(a,**pargs,c) #Error! def kwonly(a,**,c) #Error! 这就意味着,在一个函数的头部,keyword-only参数必须编写在**args任意关键字形式之前,且在*args任意位置形式之后。 实际上,在函数调用中,类似的排序规则也是成立的:当传递keyword-only参数的时候,它们必须出现在一个**args形式之前。keyword-only参数可以编写在*arg之前或者之后,并且可能包含在**args中: def f(a,*b,c=6,**d):print(a,d) f(1,*(2,3),**dict(x=4,y=5)) #Prints:1 (2,3) 6 {'x':4,'y':5} f(1,y=5),c=7) #Error! f(1,c=7,y=5)) #Prints:1 (2,3) 7 {'x':4,y=5,c=7)) #Prints:1 (2,'y':5} Python作用域 在一个Python程序只用变量名时,Python创建、改变或查找变量名都是在所谓的命名空间(一个保存变量名的地方)中进行的。也就是说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围,也即决定了它存在于哪个命名空间中。 除了打包程序之外,函数还为程序增加了一个额外的命名空间层:默认情况下,一个函数所有变量名都是与函数的命名空间相关联的。这意味着: 一个在def内的定义的变量能够在def内的代码使用,不能在函数的外部应用这样的变量名。 内嵌的模块是全局作用域 每个模块都是一个全局作用域(也就是说,一个创建于模块文件顶层的变量的命名空间)。对于模块外部来说,该模块的全局变量就成为了这个模块对象的属性,但是在这个模块中能够像简单的变量一样使用。
Python按顺序在上面4个作用域中查找变量,并且在第一个能够找到这个变量名的地方停下来,如果在这4个作用域中都没找到,Python会报错。 这里需要强调的是,上面四个作用域是函数中代码的搜索过程,也就是说,在函数中能直接使用上一层中的变量! s=10 def times(x,y): x=s return x*y times(3,4) #return 40 not 12 >>内置作用域 import builtins dir(builtins) 因此,事实上有两种方法可以引用一个内置函数:通过LEGB法则带来的好处,或者手动导入builtin模块。其中第二种方法在一些复杂的任务里是很有用的,因为一些局部变量有可能会覆盖内置的变量或函数。再次强调的是,LEGB法则只使它找到的第一处变量名的地方生效! global语句 global语句是一个命名空间的声明,它告诉Python解释器打算生成一个或多个全局变量,也就是说,存在于整个模块内部作用域(命名空间)的变量名。关于全局变量名: 全局变量是位于模块文件内部顶层的变量名。 X=88 def func(): global X X = 99 func() print(X) #Prints 99 作用域和嵌套函数 这部分内容是关于LEGB查找法则中E这一层的,它包括了任意嵌套函数内部的本地作用域。嵌套作用域有时也叫做静态嵌套作用域。实际上,嵌套是一个语法上嵌套的作用域,它是对应于程序源代码的物理结构上的嵌套结构。 >>嵌套作用域的细节 一个引用(X)首先在本地(函数内)作用域查找变量名X;之后会在代码的语法上嵌套了的函数中的本地作用域,从内到外查找;之后查找当前的全局作用域(模块文件);最后在内置作用域内(模块builtin)。全局声明将会直接从全局(模块文件)作用域进行搜索。其实就是从引用X的地方开始,一层一层网上搜索,直到找到的第一个X。 X = 99 def f1(): X = 88 def f2(): print(X) f2() f1() #Prints 88:enclosing def local 首先需要说明的是,上面这段代码是合法的,def是一个简单的执行语句,可以出现在任意其他语句能够出现的地方,包括嵌套在另一个def之中。代码中,f2是在f1中定义的函数,在此情况下,f2是一个临时函数,仅在f1内部执行的过程中存在(并且只对f1中的代码可见)。通过LEGB查找法则,f2内的X自动映射到了f1的X。 值得注意的是,这个嵌套作用域查找在嵌套的函数已经返回后也是有效的。 X = 99 def f1(): X = 88 def f2(): print(X) #Remember X in enclosing def scope return f2 #Return f2 but don't call it action = f1() #Make return function action() #Call it now:Prints 88 上述代码中,不管调用几次action函数,返回值都是88,f2记住了f1中嵌套作用域中的X,尽管此时f1已经不处于激活的状态。 工厂函数 上述这些行为有时叫做闭合(closure)或者工厂函数――一个能够记住嵌套作用域的变量值的函数,即使那个作用域也许已经不存在了。通常来说,使用类来记录状态信息时更好的选择,但是像这样的工厂函数也提供了一种替代方案。 具体的例子: def maker(N): def action(X): return X ** N return action f=maker(2) #Pass 2 to N f(3) #Pass 3 to X,N remembers 2: 3**2,Return 9 f(4) #return 4**2 g=maker(3) #g remembers 3,f remembers 2 g(3) #return 27 f(3) #return 9 从上面代码中可以看到,f和g函数分别记录了不同的N值,也就是记录了不同的状态,每一次对这个工厂函数进行赋值,都会得到一个状态信息的集合,每个函数都有自己的状态信息,由maker中的变量N保持。 作用域与带有循环变量的默认参数相比较 在已给出的法则中有一个值得注意的特例:如果lambda或者def在函数中定义,嵌套在一个循环之中,并且嵌套的函数引用了一个上层作用域的变量,该变量被循环所改变,所有在这个循环中产生的函数都将会有相同的值――在最后一次循环中完成时被引用变量的值。具体的例子: def makeActions(): acts=[] for i in range(5): #Tries to remember each i acts.append(lambda x: i ** x) #All remember same last it return acts 尽管是在尝试创建一个函数列表,使得每个函数拥有不同的状态值,但是事实上,这个列表中的函数的状态值都是一样的,是4。因为嵌套作用域中的变量在嵌套的函数被调用时才进行查找,所以它们实际上记住的是同样的值(在最后一次循环迭代中循环变量的值)。 为了能让这类代码能够工作,必须使用默认参数把当前的值传递给嵌套作用域的变量。因为默认参数是在嵌套函数创建时评估的(而不是在其稍后调用时),每一个函数记住了自己的变量i的值。 def makeActions(): acts=[] for i in range(5): #Use default instead acts.append(lambda x,i=i: i ** x) #Remember current i return acts { nonlocal语句 事实上,在Python3.0中,我们也可以修改嵌套作用域变量,只要我们在一条nonlocal语句中声明它们。使用这条语句,嵌套的def可以对嵌套函数中的名称进行读取和写入访问。nonlocal应用于一个嵌套的函数的作用域中的一个名称,而不是所有def之外的全局模块作用域――它们可能只存在于一个嵌套的函数中,并且不能由一个嵌套的def中第一次赋值创建。 换句话说,nonlocal即允许对嵌套的函数作用域中的名称变量赋值,并且把这样的名称作用域查找限制在嵌套的def。 >>nonlocal基础 def func(): nonlocal name1,name2... 这条语句允许一个嵌套函数来修改在一个语法嵌套函数的作用域中定义的一个或多个名称。在Python 2.X中,当一个函数def嵌套在另一个函数中,嵌套的函数可以引用上一层函数中定义的各种变量,但是不能修改它们。在Python3.0中,在一条nonlocal语句中声明嵌套的作用域,使得嵌套的函数能够赋值,并且由此也能够修改这样的名称。 除了允许修改嵌套的def中的名称,nonlocal语句还加快了引用――就像global语句一样,nonlocal使得对该语句中列出的名称的查找从嵌套的def的作用域中开始,而不是从声明函数的本地作用域开始,也就是说,nonlocal也意味着”完全略过我的本地作用域”。 实际上,当执行到nonlocal语句的时候,nonlocal中列出的名称必须在一个嵌套的def中提前定义过,否则,将会产生一个错误。直接效果和global很相似:global意味着名称位于上一层的模块中,nonlocal意味着它们位于一个上一层的def函数中。nonlocal甚至更加严格――作用域查找只限定在嵌套的def。也就是说,nonlocal只能出现在嵌套的def中,而不能在模块的全局作用域中或def之外的内置作用域中。 当在一个函数中使用的时候,global和nonlocal语句都在某种程度上限制了查找规则: global使得作用域查找从嵌套的模块的作用域开始,并且允许对那里的名称赋值。如果名称不存在与该模块中,作用域查找继续到内置作用域,但是,对全局名称的赋值总是在模块作用域中创建或修改它们。 def tester(start): state = start #each call gets its own state def nested(label): nonlocal state #remember state in enclosing scope print(label,state) state+=1 #Allowed to change it if onolocal return nested F = tester(0) #Increments state on each call F('spam') #Prints:spam 0 F('ham') #Prints:ham 1 F('eggs') #Prints:eggs 2 边界情况 当执行一条nonlocal语句时,nonlocal名称必须已经在一个嵌套的def作用域中赋值过,否则将会得到一个错误。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |