函数
函数
当函数遇到了 def
其会生成创建函数的指令,即其并不仅仅只是创建函数的关键字,可以在任何地方动态的创建函数
一个函数的完整信息由函数和代码组成,其中一部分为 Pycodeobject
包含字节码等执行数据,而 Pyfunctionobject
包含了函数的状态信息
函数声明如下
# 创建函数命名为func
def func():
print('hello world')
创建
包括函数在内的对象都为第一类对象,可以当做其余函数的参数或者返回值
- 由于名字是唯一主键标识,因此函数在同一范围内不能被重载
- 任何函数都有返回值,如果不指定返回值则默认为
None
- 支持递归调用,但是不进行尾地柜优化,最大递归深度为
sys.getrecursionlimit()
def func(name):
if name == 'a':
def a():
pass
return a
else:
def b():
pass
return b
# 获取函数名称
func('a').__name__
参数
在python中函数传参灵活多变,可以是位置参数,可以是命名参数,也可以是默认参数
def test(a, b):
print(a, b)
test(1, 'a') # 位置参数
test(1, b=10) # 命名参数
在python中支持默认参数,会在创建的时候开辟地址空间,所有调用对象都指向同一对象,如果是可变类型其地址永远不会发生改变
def test(x,y=[]):
y.append(x)
return y
print(test(1)) # 传递参数
print(test(2)) # 传递参数
print(test(1,[])) # 显示传递参数会改变上述地址空间
print(test(3)) # 再次使用默认值
关键字参数后面不能为位置参数,但是可以为可变参数
def test(a=1,b):
print(a,b)
一般常用 *args
接收多余的位置参数,用 **kwargs
接收多余的关键字参数,这两个参数名称只是惯例使用,本质上可以随便自定义
- args会接受多余的位置参数以元组的形式进行保存
- kwargs会接收多余的关键字参数以字典的形式进行保存其中关键字名称为字典的key值为字典的value
def test(*args,**kwargs):
print(args)
print(kwargs)
test(1,2,2,3,4,a=10,b=20)
可以使用单个的 *
或者 **
将列表或者字典打散
def test(a,b,*args,**kwargs):
print(a,b) # a b接受两个参数 1 2
print(args) # 多余的位置参数被接受成元组形式
print(kwargs) # 接受关键字参数
test(*range(1,6),**{'name':"SR","age":18})
单个 "*" 展开序列类型,或者仅是字典的主键列表。"**" 展开字典键值对。但如果没有变参收集,展开后多余的参数将引发异常
def test(a,b):
print(a,b)
d = dict(a=1,b=2)
test(*d) # 对于字典只能获取key
test(**d) # 展开字典test(a=1,b=2)
d = dict(a=1,b=2,c=3)
test(*d) # 报错 没有位置参数接受c
test(**d) # 保存没有关键字参数接受c=3
作用域
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
函数的形参和内部变量以key/value形式存储在 locals
命名空间中
def test(a,*args,**kwargs):
age = 18
print(locals())
test(1,2,3,{'name':"SR"},x=10,y=20)
除非使用 gloabl
和 nonlocal
等语句对变量进行特殊声明,否则声明的代码总是新建一个关联对象存储在 lcoals
命名空间,如果进行赋值此时是将值内存地址指向新的对象,不会改变原有值
x = 10
print(id(x)) # 9075200
def test():
x = 20
print("The id is %s The value is %s" % (id(x),x)) # The id is 9075520 The value is 20
test()
在函数内部引用外部变量的时候将按照 LEGB
形式进行查找
Locals
:函数内部本地查找,包括局部变量和形参等enclosing function
: 外部嵌套函数的名称空间globals
:函数所在的模块名称空间__builtins__
:内置模块所在的名称空间
闭包
- 内部函数被外部函数所包裹
- 内部函数对外部函数中的变量有引用
- 外部函数的返回值为内部函数
def test():
x = [1,2]
print(id(x))
def a():
x.append(3)
print(id(x))
def b():
print(id(x),x)
return a,b
a,b = test()
a()
b()
test在创建函数的时候,内部函数a与b作用域延伸到外部函数test同时对变量x进行了自由绑定,返回审查的a,b对象发现Python在__code__
属性中保存局部变量和自由变量的名称
# 查看test函数创建时候的局部变量
a.__code__.co_varnames
# 查看自由变量
a.__code__.co_freevars
自由变量x绑定在a,b的 __closure__
属性中,在 a.__closure__
中保存各个元素对应于 a.__code__.co_freevars
中的名称这些元素都是可以 cell
对象在 cell_content
属性中保存着真正的值
a.__closure__
a.__closure__[0].cell_contents # [1,2,3]
闭包延迟
只有当运行内部函数的时候才会绑定外部函数的自由变量,当内部函数没有被调用的时候不会绑定外部函数的值,此现象称之为延迟绑定
def test():
return [lambda x : i * x for i in range(4)]
# 为什么是 [6,6,6,6] 而不是 [0,1,2,4]
[t(2) for t in test()]
# 修改上述上述代码
def test():
func_list = []
for i in range(4):
def a(x):
return x * i
func_list.append(a)
return func_list
[t(2) for t in test()]
在上述代码中test只是将函数对象添加进入列表中,并没有调用函数a,在python中相对而言局部变量绑定属于值,全局变量绑定的属于内存地址,在for循环中相对于函数a来说i属于全局变量,因此绑定的属于内存地址,最终内存地址所对应的值为3,因此最终i为3
# 如何生成 [0,1,2,4]
# 按照上述思路 只需将i更改为局部变量绑定值即可
def test():
func_list = []
for i in range(4):
def a(x , i=i):
return x * i
func_list.append(a)
return func_list
[t(2) for t in test()]
在python当中如果函数遇到 yield
时候会让函数从上回 yield
返回地点继续执行同时交给函数调用者一个返回值,再次返回运行函数,直到遇到 yield
再次返回一个新的值,因此可以使用 yield
延迟挂起解决闭包延迟
def test():
for i in range(4):
def a(x):
return x * i
yield a
[t(2) for t in test()]