函数,参数及参数解构, 返回值,作用域
函数,参数及参数解构
函数
数学定义: y=f(x),y是x的函数,x是自变量.
python函数: 完成一定的功能,由若干语句组成的语句块,函数名称,参数列表构成,它是组织代码的最小单位.
函数的作用:
结构化变成对代码的最基本的封装,一般按照功能组织一段代码.
封装的目的为了复用,减少冗余代码.
代码更加简洁美观,可读易懂.
函数的分类:
内建函数: 如max(),reversed()等.
库函数: 如math.ceil()等.
函数定义,调用
def语句定义函数.
语法:
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
函数名就是标识符,命名要求一样.
语句块必须缩进,约定4个空格.
python的函数没有return语句,隐式会返回一个None值.
定义中的参数列表成为形式参数,只是一种符号表达,简称形参.
调用:
函数定义,只是声明了一个函数,它不会被执行,需要调用.
调用的方式,就是函数名加上小括号,括号内写上参数.
调用时写的参数是实际参数,是实实在在传入的值,简称实参.
函数举例:
1 2 3 4 5 |
def add(x, y): result = x + y return result out = add(4, 5) print(out) # 9 |
上面只是一个函数的定义,有一个函数add,接收2个参数.
计算的结果,通过返回值返回.
调用通过函数名add加2个参数,返回值可使用变量接收.
定义需要在调用前,也就是说调用时,已经被定义过了,否则抛异常NameError.
函数是可调用的对象,callable().
函数参数
参数调用时传入的参数要和定义的个数相匹配(可变参数例外).
位置参数:
def f(x,y,z) 调用使用f(1,3,5).
按照参数定义顺序传入实参.
关键字参数:
def f(x,y,z)调用使用f(x=1,y=3,z=5).
使用形参的名字来出入实参的方式,如果使用了形参名字,那么传参顺序就可以和定义顺序不同.
传参:
f(z=None,y=10,x=[1])
f((1,),z=6,y=4.1)
f(y=5,z=6,2)
要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的.
参数规则: 参数列表参数一般顺序是,普通参数,缺省参数,可变位置参数,keyword-only参数(可带缺省值),可变关键字参数.
1 2 3 4 5 6 7 |
In [44]: def fn(x,y,z=3,*arg,m=4,n,**kwargs): ...: print(x,y,z,m,n) ...: print(args) ...: print(kwargs) ...:
In [45]: |
注:
代码应该易读易懂,而不是为难别人.
按照书写习惯定义函数参数.
函数参数默认值
参数默认值:
定义时,在形参后跟上一个值.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def add(x=4,y=5): result = x + y print(result)
add(6,10) add(6,y=7) add(x=5) add() add(y=7) # add(x=5,6) # error. # add(y=8,4) # error. add(x=5,y=6) add(y=5,x=6) |
注: def add(x=4, y)是错误的.
作用:
参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值.
参数非常多时,并不需要用户每次都输入所有的参数,简化函数调用.
举例:
定义一个函数login,参数名称为host,port,username,password.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def login(host='127.0.0.1', port='8080', username='ames', password='ames'): print('{}:{}@{}/{}'.format(host, port, username, password)) login() login('127.0.0.1', 80, 'tom', 'tom') login('127.0.0.1', username='root') login('localhost', port=80, password=1234) login(port=80,password='python', host='www')
# 运行结果:
127.0.0.1:8080@ames/ames 127.0.0.1:80@tom/tom 127.0.0.1:8080@root/ames localhost:80@ames/1234 www:80@ames/python |
可变参数
一个形参可以匹配任意个参数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
In [1]: def add(nums): ...: sum = 0 ...: for x in nums: ...: sum += x ...: return sum ...:
In [2]: add([1, 3, 5]) Out[2]: 9
In [3]: add([2, 4, 6]) Out[3]: 12
In [4]: |
位置参数的可变参数: 有多个数,需要累加求和.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
In [4]: def add(*nums): ...: sum = 0 ...: print(type(nums)) ...: for x in nums: ...: sum += x ...: print(sum) ...:
In [5]: add(3, 6, 9) <class 'tuple'> 18
In [6]: |
在形参前使用*表示该形参是可变参数,可以接收多个实参.
收集多个实参为一个tuple.
关键字参数的可变参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
In [6]: def showconfig(**kwargs): ...: for k,v in kwargs.items(): ...: print('{} = {}'.format(k,v)) ...:
In [7]: showconfig(host='127.0.0.1', port=8080, username='ames', password=123456) username = ames host = 127.0.0.1 port = 8080 password = 123456
In [8]:
# 运行结果: password = 123456 port = 8080 username = ames host = 127.0.0.1 |
形参前使用**符号,表示可以接收多个关键字参数.
收集的实参名称和值组成一个字典.
可变参数混合使用:
def showconfig(username,password,**kwargs)
def showconfig(username,*args,**kwargs)
def showconfig(username,password,**kwargs,*args)
总结:
可变参数分为位置可变参数和关键字可变参数.
位置可变参数在形参前使用一个星号*.
关键字可变参数在形参前使用两个星号**.
位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict.
混合使用参数时,可变参数要放在参数列表的最后,普通参数需要放到参数列表前面,位置可变参数需要放在关键字可变参数之前.
可变参数举例:
举例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
In [8]: def fn(x,y,*args,**kwargs): ...: print(x) ...: print(y) ...: print(args) ...: print(kwargs) ...:
In [9]: fn(3,5,7,9,10,a=1,b='ames') 3 5 (7, 9, 10) {'a': 1, 'b': 'ames'}
In [10]: fn(13,15) 13 15 () {}
In [11]: fn(23,25,27) 23 25 (27,) {}
In [12]: fn(33,35,a=1,b='ames') 33 35 () {'a': 1, 'b': 'ames'}
In [13]: fn(7,9,y=5,x=3,a=1,b='ames') --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-13-a0c84e79923c> in <module>() ----> 1 fn(7,9,y=5,x=3,a=1,b='ames')
TypeError: fn() got multiple values for argument 'y' # 错误,7和9分别赋给了x,y,又y=5 x=3,x和y的值重复.
In [14]: |
举例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
In [14]: def fn(*args, x, y, **kwargs): ...: print(x) ...: print(y) ...: print(args) ...: print(kwargs) ...:
In [15]: fn(3, 5) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-15-4ac8ce4a4cba> in <module>() ----> 1 fn(3, 5)
TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
In [16]: fn(13, 15, 17) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-16-75940373c950> in <module>() ----> 1 fn(13, 15, 17)
TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
In [17]: fn(23, 25, a=1, b='ames') --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-17-709612ad798f> in <module>() ----> 1 fn(23, 25, a=1, b='ames')
TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
In [18]: fn(37, 39, y=35, x=33, a=1, b='ames') 33 35 (37, 39) {'a': 1, 'b': 'ames'}
In [19]: |
keyword-only参数
keyword-only参数 (python3加入).
如果在一个星号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数,而是keyword-only参数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
In [19]: def fn(*args, x): ...: print(x) ...: print(args) ...:
In [20]: fn(3, 5) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-20-4ac8ce4a4cba> in <module>() ----> 1 fn(3, 5)
TypeError: fn() missing 1 required keyword-only argument: 'x'
In [21]: fn(13, 15, 17) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-21-57115e70fa2e> in <module>() ----> 1 fn(13, 15, 17)
TypeError: fn() missing 1 required keyword-only argument: 'x'
In [22]: fn(23, 25, x = 27) 27 (23, 25)
In [23]: |
注: 如上错误,args可看做已经截获了所有位置参数,x不使用关键字参数就不可能拿到实参.
以上函数如果换成def fn(**kwargs,x),会直接报语法错误,可以理解为kwargs会截获所有的关键字,就算写了x=5,x也永远得不到这个值,所以语法错误.
keyword-only参数的另一种形式:
1 2 3 4 5 6 7 8 |
In [23]: def fn(*,x,y): ...: print(x,y) ...:
In [24]: fn(x=5,y=6) 5 6
In [25]: |
*号之后,普通形参都变成了必须给出的keyword-only参数.
可变参数和参数默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
In [25]: def fn(*args,x=5): ...: print(x) ...: print(args) ...:
In [26]: fn() # 等价于fn(x=5) 5 ()
In [27]: fn(5) 5 (5,)
In [28]: fn(x=6) 6 ()
In [29]: fn(1,2,3,x=10) 10 (1, 2, 3)
In [30]: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
In [30]: def fn(y,*args,x=5): ...: print('x={},y={}'.format(x,y)) ...: print(args) ...:
In [31]: fn() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-31-fd7064640434> in <module>() ----> 1 fn()
TypeError: fn() missing 1 required positional argument: 'y'
In [32]: fn(5) x=5,y=5 ()
In [33]: fn(x=6) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-33-704ccb59ea7c> in <module>() ----> 1 fn(x=6)
TypeError: fn() missing 1 required positional argument: 'y'
In [34]: fn(1,2,3,x=10) x=10,y=1 (2, 3)
In [35]: fn(y=17,2,3,x=10) File "<ipython-input-35-717813843388>", line 1 fn(y=17,2,3,x=10) ^ SyntaxError: positional argument follows keyword argument
In [37]: fn(17,2,3,x=10) x=10,y=17 (2, 3)
In [36]: fn(1,2,y=3,x=10) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-36-c795acbab6ef> in <module>() ----> 1 fn(1,2,y=3,x=10)
TypeError: fn() got multiple values for argument 'y'
In [38]: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
In [38]: def fn(x=5, **kwargs): ...: print('x={}'.format(x)) ...: print(kwargs) ...:
In [39]: fn() x=5 {}
In [40]: fn(5) x=5 {}
In [41]: fn(x=6) x=6 {}
In [42]: fn(y=3,x=10) x=10 {'y': 3}
In [43]: fn(3,y=10) x=3 {'y': 10}
In [44]: |
参数解构
参数解构:
给函数提供实参时,可在集群类型前使用*或**,把集群类型的解构解开,提取出所有元素作为函数的实参.
非字典类型使用*解构成位置参数.
字典类型使用**解构成关键字参数.
提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
In [45]: def add(x,y): ...: return x+y ...:
In [46]: add(*(4,5)) Out[46]: 9
In [47]: add(*[4,5]) Out[47]: 9
In [48]: add(*{4,6}) Out[48]: 10
In [49]: d = {'x':5, 'y':6}
In [50]: add(**d) Out[50]: 11
In [51]: add(**{'x':5, 'y':6}) Out[51]: 11
In [52]: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
In [54]: def add(*iterable): ...: result = 0 ...: for x in iterable: ...: result += x ...: return result ...:
In [55]: add(1,2,3) Out[55]: 6
In [56]: add(*[1,2,3]) Out[56]: 6
In [57]: add(*range(10)) Out[57]: 45 |
练习:
1.编写一个函数,能够接受至少2个参数,返回最小值和最大值.
1 2 3 4 5 6 7 8 9 |
import random
def double_values(*nums): print(nums) return max(nums), min(nums) print(*double_values(*[random.randint(10, 20) for _ in range(10)])) # 两次解构 # 运行结果: (18, 16, 16, 12, 13, 13, 14, 20, 18, 16) 20 12 |
- 编写一个函数,接受一个参数n,n为正整数,左右两种打印方式.要求数字必须对齐.
1 2 3 4 5 6 7 |
def show(n): tail = ' '.join([str(i) for i in range(n, 0, -1)]) width = len(tail) for i in range(1, n): print("{:>{}}".format(" ".join([str(j) for j in range(i, 0, -1)]),width)) print(tail) show(12) |
1 2 3 4 5 6 7 |
def showtail(n): tail = ' '.join([str(i) for i in range(n, 0, -1)]) print(tail) for i in range(len(tail)): if tail[i] == ' ': print(' '*i, tail[i+1:]) showtail(12) |
插入排序
函数返回值,作用域
函数的返回值
举例:
1 2 3 4 5 6 7 8 9 |
def showplus(x): print(x) return x+1 print(x+1) # 该行不会执行. print(showplus(5))
# 运行结果: 5 6 |
多条return语句:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def guess(x): if x > 3: return '>3' else: return '<=3' print(guess(10)) ------------------------------------------- def showplus(x): print(x) return x+1 return x+2 # 该行不会执行. print(showplus(5))
# 运行结果: 5 6 |
1 2 3 4 5 6 7 8 9 10 11 12 |
def fn(x): for i in range(x): if i > 3: return i else: print('{} is not greater than 3'.format(x)) print(fn(5)) # 运行结果: 4 print(fn(3))
# 运行结果: 3 is not greater than 3 None # 所有函数都有函数值,如果没有return语句,隐式调用return None. |
总结:
python函数使用return语句返回'返回值'.
所有函数都有函数值,如果没有return语句,隐式调用return None.
return语句并不一定是函数的语句块的最后一条语句.
一个函数可以存在多个return语句,但是只有一条可以被执行.如果没有一条return语句被执行到,隐式调用return None.
如果有必要,可以显示调用return None,可以简写为return.
如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其他语句就不会被执行.
作用: 结束函数调用和返回值.
返回多个值:
1 2 3 4 5 6 7 |
def showlist(): return [1, 3, 5] print(showlist()) # 运行结果: [1, 3, 5]
def showlist(): return 1, 3, 5 print(showlist()) # 运行结果: (1, 3, 5) |
注:
函数不能同时返回多个值.
return [1,3,5] 即指明返回一个列表,是一个list对象.
return 1,3,5 看似返回多个值,隐式的被python封装成了一个元组.
1 2 3 4 |
def showlist(): return 1,3,5 x, y, z = showlist() print(x, y, z) # 运行结果: 1 3 5 |
函数嵌套
函数嵌套:
在一个函数中定义了另一个函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
In [4]: def outer(): ...: def inner(): ...: print('inner') ...: print('outer') ...: inner() ...:
In [5]: outer() outer inner
In [6]: inner() --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-6-159a447ee30d> in <module>() ----> 1 inner()
NameError: name 'inner' is not defined
In [7]: |
注:
函数有可见范围,这就是作用域的概念.
内部函数不能被外部直接使用,会抛异常NameError,如上代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def outer(): def inner(): print('inner') return 'xx' # return None print('outer') inner() return inner(),'p' print(outer())
# 执行结果 outer inner inner ('xx', 'p') # 两次return的结果组成了一个元组.
|
作用域
作用域: 一个标识符的可见范围即标识符的作用域.一般常说的是变量的作用域.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
In [9]: x = 5 ...: def foo(): ...: print(x) ...:
In [10]: foo() 5
In [11]: |
In [13]: x = 5 ...: def foo(): ...: x += 1 # x = x + 1 ...: print(x) ...:
In [14]: foo()
# 运行报错: UnboundLocalError: local variable 'x' referenced before assignment
In [15]: |
全局作用域:
在整个程序运行环境中都可见.
局部作用域:
在函数,类等内部可见.
局部变量使用范围不能超过其所在的局部作用域.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
In [31]: def fn1(): ...: x = 1 # 局部作用域,在fn1内. ...: def fn2(): ...: print(x) ...:
In [32]: print(x) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-32-57a99df8cebf> in <module>() ----> 1 print(x)
NameError: name 'x' is not defined
In [33]: |
嵌套结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def outer1(): o = 65 def inner(): print('inner {}'.format(o)) print(chr(o)) print('outer {}'.format(o)) inner()
outer1()
# 运行结果: outer 65 inner 65 A
|
def outer2(): o = 65 def inner(): o = 97 print('inner {}'.format(o)) print(chr(o)) print('outer {}'.format(o)) inner()
outer2()
# 运行结果: outer 65 inner 97 a
|
从嵌套结构例子看出:
外层变量作用域在内层作用域可见.
内层作用域inner中,如果定义了o=97,相当于当前作用域中重新定义了一个新的变量o,但是这个o并没有覆盖外层作用域outer中的o.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
In [1]: x = 5
In [2]: def foo(): ...: y = x + 1 ...: x += 1 ...: print(x) ...:
In [3]: foo() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-3-624891b0d01a> in <module>() ----> 1 foo()
<ipython-input-2-4a190c7f9e12> in foo() 1 def foo(): ---> 2 y = x + 1 3 x += 1 4 print(x) 5
UnboundLocalError: local variable 'x' referenced before assignment
In [4]: def foo(): ...: y = x + 1 ...: # x += 1 ...: print(x) ...:
In [5]:
In [5]: foo() 5
In [6]: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
In [14]: x = 5
In [15]: def foo(): ...: x += 1 ...: return x ...:
In [16]: foo() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-16-624891b0d01a> in <module>() ----> 1 foo()
<ipython-input-15-36b35103f745> in foo() 1 def foo(): ----> 2 x += 1 3 return x
UnboundLocalError: local variable 'x' referenced before assignment
In [17]:
注意: x += 1 其实是 x = x + 1 相当于在foo内部定义一个局部变量x,那么foo内部所有x都是这个局部变量x了. 但是这个x还没完成复制,就被右边拿来做加1操作了,故报错.
|
全局变量: global
1 2 3 4 5 6 7 8 9 10 11 12 13 |
In [20]: x = 5
In [21]: def foo(): ...: global x ...: x += 1 ...: return x ...:
In [22]: foo() Out[22]: 6
In [23]: |
使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x.
全局作用域中必须有x的定义.
如果全局作用域中没有x定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
In [1]: def foo(): ...: global x ...: x = 10 ...: x += 1 ...: print(x) ...:
In [2]: foo() 11
In [3]: print(x) 11
In [4]: |
使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x.
但是,x = 10赋值即定义,x在内部作用域为一个外部作用域的变量赋值,所以x += 1不会报错.注意这里的x的作用域还是全局的.
global总结:
x+=1这种是特殊形式产生的错误的原因,先引用后赋值,而python动态语言是赋值才算定义,才能被引用.解决办法是在这条语句前增加x=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义.
内部作用域使用x=5之类的赋值语句会重新定义局部作用域使用的变量x,但是一旦这个作用域中使用的global声明为全局的,那么x=5相当于在为全局作用域的变量x赋值.
global使用原则:
外部作用域变量在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离.
如果函数需要使用外部全局变量,使用函数的形参传参解决.
闭包
自由变量: 未在本地作用域中定义的变量.例如定义在内存函数外的外层函数的作用域中的变量.
闭包: 就是一个概念,出现在嵌套函数中,指内层函数引用到外层函数的自由变量,就形成了闭包,JavaScript就是如此.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
In [8]: def counter(): ...: c = [0] ...: def inc(): ...: c[0] += 1 ...: return c[0] ...: return inc ...:
In [9]: foo = counter()
In [10]: print(foo(), foo()) 1 2
In [11]: c = 100
In [12]: print(foo()) 3
In [13]: |
代码解析:
第四行没有报错,因为c已经在counter函数中定义过了,而且inc中的使用方式是为c的元素修改值,而不是重新定义变量.
第十四行定义的c和counter中的c不一样,而inc引用的是自由变量而不是counter的变量c.
这是python2中实现闭包的方式,python3还可以使用nonlocal关键字.
如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
In [15]: def counter(): ...: count = 0 ...: def inc(): ...: count += 1 ...: return count ...: return inc ...:
In [16]: foo = counter()
In [17]: foo() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-17-624891b0d01a> in <module>() ----> 1 foo()
<ipython-input-15-b7d707b2e550> in inc() 2 count = 0 3 def inc(): ----> 4 count += 1 5 return count 6 return inc
UnboundLocalError: local variable 'count' referenced before assignment
In [18]: foo() |
以上报错,使用global可以解决,但是这样使用的是全局变量,而不是闭包.
如果要使用普通变量的闭包,python3可以使用nonlocal.
nonlocal关键字
使用了nonlocal关键字,将变量标记为在上级的局部作用域中定义,但不能是全局作用域中定义.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
In [20]: def counter(): ...: count = 0 ...: def inc(): ...: nonlocal count ...: count += 1 ...: return count ...: return inc ...:
In [21]: foo = counter()
In [22]: foo() Out[22]: 1
In [23]: foo() Out[23]: 2
In [24]: |
In [26]: a = 50 ...: def counter(): ...: nonlocal a ...: a += 1 ...: print(a) ...: count = 0 ...: def inc(): ...: nonlocal count ...: count += 1 ...: return count ...: return inc File "<ipython-input-26-a29a1eb62faa>", line 3 nonlocal a ^ SyntaxError: no binding for nonlocal 'a' found # nonlocal 要绑定一个局部变量.
In [27]: |
count是外层函数的局部变量,被内层函数引用.
内部函数使用nonlocal关键字声明count变量在上一级作用域中.
上面左边代码可以正常使用,且形成闭包;右边代码不能正常运行,变量a不能在全局作用域中.
默认值的作用域
默认值举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
In [27]: def foo(xyz=1): ...: print(xyz) ...:
In [28]: foo() 1
In [29]: foo() 1
In [30]: print(xyz) ------------------------------------------------------------NameError Traceback (most recent call last)<ipython-input-30-a2bcd278357a> in <module>() ----> 1 print(xyz)
NameError: name 'xyz' is not defined
In [31]: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
In [32]: def foo(xyz=[]): ...: xyz.append(1) ...: print(xyz) ...:
In [33]: foo() [1]
In [34]: foo() [1, 1]
In [35]: print(xyz) ------------------------------------------------------------NameError Traceback (most recent call last)<ipython-input-35-2cf093bba2e6> in <module>() ----> 1 print(xyz)
NameError: name 'xyz' is not defined
In [36]: |
以上示例第二次调用foo函数打印[1, 1],是因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期.
查看属性: foo.__defaults__
如下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
In [39]: def foo(xyz=[], u='abc', z=123): ...: xyz.append(1) ...: return xyz ...:
In [40]: foo(), id(foo) Out[40]: ([1], 2239176151928)
In [41]: foo.__defaults__ Out[41]: ([1], 'abc', 123)
In [42]: foo(), id(foo) Out[42]: ([1, 1], 2239176151928)
In [43]: foo.__defaults__ Out[43]: ([1, 1], 'abc', 123)
In [44]: |
函数地址并没有变,就是说函数这个对象的地址没有变,调用它,它的属性__defaults__中使用元组保存所有默认值.
xyz默认值是引用类型,引用类型的元素变动,并不是元组的变化.
非引用类型例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
In [48]: def foo(w,u='abc',z=123): ...: u='xyz' ...: z=789 ...: print(w,u,z) ...:
In [49]: print(foo.__defaults__) ('abc', 123)
In [50]: foo('ames') ames xyz 789
In [51]: print(foo.__defaults__) ('abc', 123)
In [52]: |
属性__defaults__中使用元组保存所有默认值,它不会因为在函数体内使用了它而发生改变.
可变类型默认值,如果使用默认值,就可能修改这个默认值.
有时候这个特性是好的,有时候这种特性是不好的,有副作用.
如下例子做到了按需改变:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
In [1]: def foo(xyz=[], u='abc', z=123): ...: xyz = xyz[:] # 影子拷贝. ...: xyz.append(1) ...: print(xyz) ...:
In [2]: foo() [1]
In [3]: print(foo.__defaults__) ([], 'abc', 123)
In [4]: foo() [1]
In [5]: print(foo.__defaults__) ([], 'abc', 123)
In [6]: foo([10]) [10, 1]
In [7]: print(foo.__defaults__) ([], 'abc', 123)
In [8]: foo([10,5]) [10, 5, 1]
In [9]: print(foo.__defaults__) ([], 'abc', 123)
In [10]:
# 注: 1)函数体内,不改变默认值. 2)xyz都是传入参数或者默认参数的副本,如果想修改原参数,则无能为力. |
In [10]: def foo(xyz=None, u='abc', z=123): ...: if xyz is None: ...: xyz = [] ...: xyz.append(1) ...: print(xyz) ...:
In [11]: foo() [1]
In [12]: foo.__defaults__ Out[12]: (None, 'abc', 123)
In [13]: foo() [1]
In [14]: foo.__defaults__ Out[14]: (None, 'abc', 123)
In [15]: foo([10]) [10, 1]
In [16]: foo.__defaults__ Out[16]: (None, 'abc', 123)
In [17]: foo([10,5]) [10, 5, 1]
In [18]: foo.__defaults__ Out[18]: (None, 'abc', 123)
In [19]:
# 注: 使用不可变类型默认值: 如果使用缺省值None就创建一个列表. 如果传入一个列表,就修改这个列表. |
第一种方法:
使用影子拷贝创建一个新的对象,永远不能改变传入的参数.
第二种方法:
通过值的判断就可以灵活的选择创建或修改传入对象.
这种方式灵活,应用广泛.
很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法.
变量名解析原则LEGB
Local,本地作用域,局部作用域的local命名空间.函数调用时创建,调用结束消亡.
Enclosing, python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间.
Global,全局作用域,即一个模块的命名空间.模块被import时创建,解释器退出时消亡.
Build-in, 内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡.例如print(open),print和open都是内置的变量.
所以一个名词的查找顺序就是LEGB.
函数的销毁
全局函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
In [1]: def foo(xyz=[], u='abc', z=123): ...: xyz.append(1) ...: return xyz ...:
In [2]: foo(), id(foo), foo.__defaults__ Out[2]: ([1], 1585221689688, ([1], 'abc', 123))
In [4]: def foo(jdk=[], h='abc', p=123): ...: jdk.append(1) ...: return jdk ...:
In [5]: print(foo(), id(foo), foo.__defaults__) [1] 1585222352680 ([1], 'abc', 123)
In [6]: del foo
In [7]: print(foo(), id(foo), foo.__defaults__) ------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-7-7e639523779a> in <module>() ----> 1 print(foo(), id(foo), foo.__defaults__)
NameError: name 'foo' is not defined
In [8]: |
全局函数销毁:
重新定义同名函数.
del语句删除函数对象.
程序结束时.
局部函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
In [2]: def foo(xyz=[], u='abc', z=123): ...: xyz.append(1) ...: def inner(a=10): ...: pass ...: print(inner) ...: def inner(a=100): ...: print(xyz) ...: print(inner) ...: return inner ...:
In [3]: bar = foo() <function foo.<locals>.inner at 0x000001F99DA99A60> <function foo.<locals>.inner at 0x000001F99DA99AE8>
In [4]: print(id(foo), id(bar), foo.__defaults__, bar.__defaults__) 2171610546656 2171603622632 ([1], 'abc', 123) (100,)
In [5]: del bar
In [6]: print(id(foo), id(bar), foo.__defaults__, bar.__defaults__) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-6-cbce72c73fb2> in <module>() ----> 1 print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)
NameError: name 'bar' is not defined
In [7]: |
局部函数销毁:
重新在上级作用域定义同名函数.
del语句删除函数对象.
上级作用域销毁时.