赞
踩
目录
3)内嵌函数(nested function)的作用域与lambda表达式
3) 任意参数——收集参数,解包参数,Keyword-Only参数
函数:将一些语句集合在一起的部件,能够不止一次地在程序中运行。具体来说,函数能够计算出一个返回值,并能够改变作为函数输入的参数,而这些参数在代码运行时也许每次都不相同。它是代码最大程度的重用和最小化代码冗余而提供的最基本的程序结构,也是一种将系统分割为定义好的不同部分的工具。
python函数关于有关概念介绍
def语句一般的格式:
def包含了首行并有一个代码块跟随在后边。def的首行定义了函数名,赋值给了函数对象,并在括号中包含了0个或者以上的参数(也叫形参)
函数主体往往包含了一条return语句。return包含一个对象表达式,这个对象是函数的结果。
当它运行的时候,它创建一个新的函数对象并将其赋值给一个变量名。(python所有语句都是实时运行的,没有像独立的编译时间这样的流程)。由于它是一个语句,def语句可以出现在任一语句可以出现的地方。例如def往往包含在模块文件中,并在模块导入时运行,函数还是可以通过嵌套在if语句中去实现不同的函数定义,也是合法的。
- test = 1
- x = 100
- y = 99
- if test:
- def func():
- print(x)
- func()
- else:
- def func():
- print(y)
- func()
-
- # 输出结果
- 100
函数是实时运行的,所以关键是那个函数名所引用的对象。下面的例子将函数赋值给一个不同的变量名,并通过新的变量名进行调用。另外,函数允许任意的属性附加到记录信息以供随后使用。
- def func():
- print('python language')
- othername = func
- othername()
-
- # 输出结果
- python language
定义:当python运行到def语句时,它将会创建一个新的函数对象,封装这个函数的代码并将这个对象赋值给变量名times
- >>> def times(x, y):
- ... return x * y
- ...
调用:在def运行之后,可以在程序中通过在函数名后增加括号调用(运行)这个函数。括号中可以包含1个或者多个对象参数,这些参数将传递给函数头部的参数名。
- >>> times(2, 4)
- 8
- >>> times('str',3)
- 'strstrstr'
在上例中,*的意义取决于x和y的对象类型。这种依赖类型的行为称为多态。实际上,在python中每个操作都是多态的操作,如print,index,*操作符等。如果传递的对象不支持这种预期的接口,python将会在*表达式运行时检测到错误,并自动抛出一个异常。在python中,代码如果只关心特定的数据类型,那么以后可能对某些兼容的对象类型并不支持,这就破坏代码的灵活性。
- def intersect(seq1, seq2):
- res = []
- for x in seq1:
- if x in seq2:
- res.append(x)
- return res
- s1 = 'SPAM'
- s2 = 'SCAM'
- print(intersect(s1, s2)) # 多态
- print(intersect([1,2,3],[2,4,3]))
-
- # 输出结果
- ['S', 'A', 'M']
- [2, 3]
以上代码并不真正意思上的交集,实际上,这个函数可以用一个单独的列表解析表达式来代替
- s1 = 'SPAM'
- s2 = 'SCAM'
- a = [x for x in s1 if x in s2]
- print(a)
这种变量只是在def内的函数中时可见的,并且仅在函数运行时存在。所以在intersect函数内所有变量都是本地变量,包括res,seq1,seq2,x。return语句返回结果对象,但是res却消失了。
python创建、改变或查找变量名都是在命名空间进行的。在代码中变量名被赋值的位置决定了这个变量名能够被访问的范围,即作用域。python将一个变量名被赋值的地方关联到一个特定的命名空间。
在默认的情况下,一个函数的所有变量名都是与函数的命名空间相关联。一个在def内定义的变量名能够被def内的代码使用,与def外相同的变量名并不冲突。
变量有3种不同的地方分配:变量在一个函数内部被赋值;一个变量在一个嵌套的def(enclosing def)中赋值,对于被嵌套的函数来说是非本地的;变量在def之外赋值,它是整个文件全局的(模块的)。
函数提供了嵌套的命名空间(作用域),使其内部使用的变量名本地化,以便函数内部使用的变量名不会与函数外的变量名冲突。函数定义了本地作用域,而模块定义的是全局作用域。
- X = 99
- def func(Y):
- Z =X +Y
- return Z
- func(1)
因为X在模块文件顶层注册的,所以它是全局变量,它能够在函数内部进行引用,而不需要特意声明为全局变量。同样,func也是全局变量,def语句在这个模块顶层将一个函数对象赋值给了变量名func。
Y和Z是本地变量,因为他们是函数定义内部进行赋值的:Z是通过=语句赋值的,而Y是由于参数总是通过赋值来进行传递的。
全局作用域的作用范围仅限于单个文件,变量名有模块文件分开,并且必须精确地导入一个模块文件才能够使用这个文件中定义的变量名。
赋值的变量名除非声明为全局变量名或非本地变量名,否则均为本地变量。如果需要给一个在函数内部却位于模块文件顶层的变量名赋值,则需要函数内部通过global语句声明。如果需要给一个嵌套的def的名称赋值,从python3.0开始,需要nonlocal语句声明它。
总之,所有其他的变量名都可以归纳为本地、全局或者内置的。在函数定义内部的尚未赋值的名称要么是一个闭包作用域的本地变量(定义在def内部),要么是位于封闭模块空间的全局变量,要么是__builtin__模块预定义的内置变量。
每次调用函数都会创建一个新的本地范围。 每次调用函数时,都会创建一个新的本地作用域——即一个命名空间,在该命名空间中通常会使用该函数内部创建的名称。 可以认为每个def语句(和lambda表达式)都定义了一个新的局部作用域,但是该局部作用域实际上对应于一个函数调用。 由于Python允许函数调用自身进行循环(递归),因此每个活动调用都会收到其自身的函数局部变量副本。
交互式运行的代码实际上是输入到一个叫做__main__的内置模块中。
LEGB原则:变量名引用查找顺序:本地L、之后是函数内E,之后是全局G,最后是内置B。变量名赋值默认情况会改变或创建本地变量。全局声明和非本地声明会将赋值的变量名映射到模块文件内部的作用域。
仅对普通的变量名有效,位于特定对象中的属性变量名则遵循不同的查找规则。
内置作用域:
它仅仅是一个名为builtins的内置模块,但是必须import builtins之后才能使用内置定义域,因为变量名builtins本生没有预先内置。
- >>> import builtins
- >>> dir(builtins)
- ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
直接写 dir(__builtins__)即可
这个列表中的变量名组成了python中的内置作用域,前一半是内置的异常,而后一半是内置函数。 由于LEGB原则,你就可以不会导入就可以使用它们。这意味着在本地作用域的变量名可能会覆盖在全局作用域和内置作用域中相同的变量名, 而全局变量名有可能覆盖内置的变量名。
它是命名空间的声明,有以下特点:
全局变量是位于模块文件内部的顶层的变量名;全局变量名如果在函数内被赋值的话,必须经过声明;全局变量名在函数内部不经声明,也可以被引用
global允许我们修改一个在一个位于模块文件顶层的def之外的名称,与nonlocal作用相同,区别是nonlocal作用于嵌套的def的本地作用域内的名称,而不是嵌套的模块。
- X = 88
- def func():
- global X
- X = 99
- func()
- print(X)
-
- # 输出结果
- # 99
可以在def的本地作用域直接创建全局作用域,它将直接映射到模块作用域。
- y,z = 1,2
- def all_gobal():
- global x
- x = y + z
- print(x)
- all_gobal()
-
- # 输出结果
- 3
全局变量的使用会使函数之间的相关性变大,会使程序变得更难理解和使用。另外,全局变量也python直接保持状态信息的方法,所以在一些程序会用一个模块文件去定义全部的全局变量,这样就没有什么不利影响。
- # first.py
- X = 99
- def setX(new):
- global X
- X = new
-
- # second.py
- import first
- print(first.X)
- first.setX(88)
- print(first.X)
-
- # 输出结果
- 99
- 88
每个模块都是自包含的命名空间,必须在第二个文件导入第一个模块才能够得到它的值。一个模块文件的全局变量一旦被导入就成为这个模块的属性,导入者自动得到这个被导入模块文件的所以全局变量的访问权。
不要直接修改另一个文件的全局变量,最好的办法是如上代码,通过调用函数,传递参数,然后得到其返回值。
以下是改变全局变量的3种方法
- # thismod.py
- var = 99
- def local():
- var = 0
-
- def glob1(): # change global war
- global var
- var += 1
-
- def glob2(): # change global war
- var = 0
- import thismod
- thismod.var += 1
-
- def glob3(): # change global war
- var = 0
- import sys
- glob = sys.modules['thismod']
- glob.var += 1
-
- def test():
- print(var)
- local();glob1();glob2(); glob3();
- print(var)
-
- # 在命令行输入
- >>> import thismod
- >>> thismod.test()
- 99
- 102
- >>> thismod.var
- 102
变量的查找法则如下:先在本地作用域查找,再向任何内嵌函数的作用域,从内向外查找,最后是全局作用域,内置作用域。
默认一个赋值(X = value)会创建或改变当前本地作用域的X。如果X在函数内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域中的名称X
- X = 99
- def f1(): # enclosinng def
- X = 88
- def f2():
- print(X) # 在内嵌函数中创建引用
- f2()
-
- f1()
-
- # 88
根据查找法则,最终f2内引用的x映射到f1的x
下面代码中,f2的函数调用是发生在f1运行后发生的,f2记住了在f1嵌套作用域中的x
- def f1():
- X = 88
- def f2():
- print(X)
- return f2
- action = f1()
- action()
-
- # 88
这种行为叫做闭合(closure)或者工厂函数——一个能够记住嵌套函数作用域的变量值的函数。
- def maker(N):
- def action(X):
- return X**N
- return action # 内嵌函数记住了N
- f = maker(2)
- print(f)
- print(f(3))
- g = maker(3)
- print(g)
- print(g(3))
-
- # 输出结果
- <function maker.<locals>.action at 0x00000241720F2EA0>
- 9
- <function maker.<locals>.action at 0x0000024174B62BF8>
- 27
-
上述代码中,函数f记住N=2,且每个函数都有自己的状态信息。
在2.2以前,内嵌函数中的变量搜索会跳过外层函数的作用域,来到全局作用域查找。使用默认参数,将外层函数的对象传递给内置函数。当然最好的方法是避免在def中嵌套def。只要第二个函数(内层)定义的运行是在第一个函数(外层)调用前就行。
lambda表达式会生成一个之后调用的函数,与一个def很像,可用在def不能用的地方,如列表和字典等。少定义内嵌函数,那么lambda才是主角。
- def func():
- x = 4
- action = (lambda n: x**n)
- return action
- x = func()
- print(x(2))
-
- # 输出结果
- 16
下面是一个特例:仍要使用默认参数保存当前值,否则是最后一次循环完成时被引用变量的值。所以lambda在使用循环变量时,才会使用默认参数。
- def makeActions():
- acts = []
- for i in range(5):
- acts.append(lambda x,i=i: i**x) # i = i,用默认参数保存当前值
- return acts # acts会有5个函数对象即acts[0]到acts[4]
- acts = makeActions()
- print(acts[0](2)) # 0**2
- print(acts[3](3)) # 3**3
-
- # 输出结果
- 0
- 27
允许一个内嵌函数(nested fuction,内层)来修改嵌套函数(enclosing function,外层)的作用域中定义的一个或多个名称。
nonlocal语句会引发变量直接限定在嵌套函数的作用域查找,因此嵌套函数的作用域必须要有该变量的定义。这一点与global不同。
- def tester(start):
- state = start
- def nested(label):
- nonlocal state
- print(label, state)
- state += 1
- return nested
- F = tester(0)
- F('spam')
- G = tester(10)
- G('ham')
- F('eggs')
-
-
- # 输出变量
- spam 0
- ham 10
- eggs 1
上面测试表明:nonlocal语句允许在内存中保持可变状态的多个副本,但仅在python3能够实现。
但在python2时,可直接把状态信息移至全局作用域。缺点:只考虑模块作用域中单个共享副本。
- def tester(start):
- global state
- state = start
- def nested(label):
- global state
- print(label, state)
- state += 1
- return nested
- F = tester(0)
- F('spam')
- F('eggs')
- G = tester(10) # 之前的状态信息被覆盖
- G('happy')
- F('ham')
-
- # 输出结果
- spam 0
- eggs 1
- happy 10
- ham 11
使用类的状态,优点:比隐式的范围查找更明确;类的每一个实例都会得到信息的一个新副本。缺点:嵌套的def比编写类简单
- class tester:
- def __init__(self, start):
- self.state = start
- def nested(self, label):
- print(label, self.state)
- self.state += 1
- F = tester(0)
- F.nested('spam')
- F.nested('ham')
-
- G = tester(42)
- G.nested('toast')
- G.nested('bacon')
-
- # 输出结果
- spam 0
- ham 1
- toast 42
- bacon 43
使用函数属性的状态
- def tester(start):
- def nested(label):
- print(label, nested.state)
- nested.state+= 1
- nested.state = start # 在nested定义后初始化state
- return nested
- F = tester(0)
- F('spam')
- G = tester(10)
- G('ham')
- F('eggs')
-
- 输出结果
- spam 0
- ham 10
- eggs 1
下面的例子说明参数的传递采用指针的方法,可变对象的局部修改会影响到调用者中的变量。
- >>> def lis(L):
- ... if len(L) > 0:
- ... L[0] = 5
- ... print(id(L))
- ...
- >>> L = [1,2,3]
- >>> lis(L)
- 1831305292296
- >>> id(L)
- 1831305292296
- >>> L
- [5, 2, 3]
参数匹配法则:在函数调用中,参数必须以此顺序出现:位置参数,后面跟着关键字参数和*sequence形式的组合,再跟着**dict形式(**dict形式必须在最后)。在函数头部,参数必须以此顺序出现:一般参数,默认参数(name = value),如果有的话,后面是*name(在python3中是*),后面跟着任何name或name = value keyword-only参数(在python3中),**name形式。
python内部是使用以下的步骤来赋值前进行参数匹配:(1)通过位置分配非关键字参数(2)通过匹配变量名分配关键字参数。(3)其他额外的非关键字参数分配到*name元组中。(4)其他额外的关键字参数分配到**name字典中。(5)用默认值分配给头部未得到的参数。
如果没有特殊的语法,python默认按照位置,通过位置从左向右匹配变量名。
关键字参数:允许通过变量名进行匹配。在调用过程中使用时,参数排列的位置并没有关系。
- >>> def f(a, b, c): print(a, b, c)
- ...
- >>> f(1,c=3,b=2) # 关键字参数
- 1 2 3
默认参数:允许创建函数可选参数,如果没有传入值,在函数运行前,参数就被赋了默认值。
- >>> def f(a, b=2, c=3):print(a,b,c)
- ...
- >>> f(1)
- 1 2 3
- >>> f(1,4,5)
- 1 4 5
*和** 是让函数支持接受任意数目的参数,它们都可以出现函数定义或者函数调用中。
收集参数:
*name:可以将位置相关的参数收集到一个新的元组中,并将这个元组赋值给name
**name:只对关键字有效,允许将这些关键字传递给一个新的字典,并将这个字典赋值给name
混合一般参数,*参数,**参数
- >>> def f(a,*paras,**kargs): print(a, paras,kargs)
- ...
- >>> f(1,2,3,x=1, y=2)
- 1 (2, 3) {'x': 1, 'y': 2}
- >>> f(1,[1,2,3,4])
- 1 ([1, 2, 3, 4],) {}
解包参数:
在调用函数时,使用*语法会解包参数的集合,使用**会以键值对的形式解包一个字典,使其成为独立的关键字参数。
- >>> def func(a,b,c,d):print(a,b,c,d)
- ...
- >>> args = (1,2,3,4)
- >>> func(*args)
- 1 2 3 4
- >>> args = {'a':5, 'b':6,'c':7,'d':8}
- >>> func(**args)
- 5 6 7 8
- >>> func(1,c=3,*(2,),**{'d':4})
- 1 2 3 4
- >>> func(1,*(2,3),d=4)
- 1 2 3 4
通过下面代码,通过传递任何发送过来的参数来支持具有任意参数的任意函数
- >>> def tracer(func,*pargs,**kargs):
- ... print('calling:',func.__name__)
- ... return func(*pargs,**kargs)
- ...
- >>> def func(a,b,c,d):
- ... return a + b + c + d
- ...
- >>> print(tracer(func,1,2,c=3,d=4))
- calling: func
- 10
keyword-only参数:
python把函数头部的排序规则通用化,允许我们指定keyword-only参数——即必须只按照关键字传递。可以在参数列表中使用一个*符号,来表示一个函数不会接受一个变量长度的参数列表,而是期待在*后面的所有参数都作为关键字传递。
- >>> def func(a,*b,c):print(a,b,c)
- ...
- >>> func(1,2,c=3)
- 1 (2,) 3
- >>> def kwonly(a,*,b,c='spam'):
- ... print(a,b,c)
- ...
- >>> kwonly(1,b='eggs')
- 1 eggs spam
- >>> def f(a,c=6,*b,**d):print(a,b,c,d) # c is not keyword-only
- ...
- >>> f(1,2,3,x=4)
- 1 (3,) 2 {'x': 4}
注意keyword-only参数必须在一个单个星号后面指定,必须编写在**args任意关键字之前。
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。