函数二
默认参数的陷阱
def func(li=[]):
li.append(1)
print(li)
func()
func([])
func()
func()
上述代码运行结果
[1]
[1]
[1, 1]
[1, 1, 1]
我们可以发现这是什么鬼!!!
如果我们的默认参数是一个可变数据类型 (列表、字典),我们要知道如果我们没有传递数据,即我们使用的是默认的列表,那么当我们多次调用时可以发现,这个列表是公用的
命名空间
什么是命名空间
命名空间,是python解释器在内存中开辟的一块 用于 存储 变量名和内存地址的对应关系的空间。
内置命名空间
概念
python解释器启动时创建的命名空间,用于存储python解释器内置的方法,例如:print input……
内置的名字在启动解释器的时候被加载进内存里
可操作空间范围
不能使用局部和全局的名字的
全局命名空间
概念
当我们执行写的代码时,开辟的命名空间,用于存储我们自己创建的变量和方法名
全局的名字在执行代码的时候被加载到内存里
可操作空间范围
可以使用内置命名空间中的名字,但是不能用局部中使用
局部命名空间
概念
当我们自己的函数被调用的时候,开辟的命名空间,用于存储函数内部的变量和方法名
当函数调用的时候才会被加载到内存里
可操作空间范围
可以使用全局、内置命名空间中的名字
命名空间的查询顺序
当一行代码要使用变量 x 的值时,Python 会到所有可用的名字空间去查找变量,按照如下顺序:
- 局部命名空间:特指当前函数或类的方法。如果函数定义了一个局部变量 x,或一个参数 x,Python 将使用它,然后停止搜索。
- 全局命名空间:特指当前的模块。如果模块定义了一个名为 x 的变量,函数或类,Python 将使用它然后停止搜索。
- 内置命名空间:对每个模块都是全局的。作为最后的尝试,Python 将假设 x 是内置函数或变量。
- 如果 Python 在这些名字空间找不到 x,它将放弃查找并引发一个 NameError 异常,如,NameError: name ‘aa’ is not defined。
嵌套函数的情况:
- 先在当前 (嵌套的或 lambda) 函数的命名空间中搜索
- 然后是在父函数的命名空间中搜索
- 接着是模块命名空间中搜索
- 最后在内置命名空间中搜索
命名空间的生命周期
不同的命名空间在不同的时刻创建,有不同的生存期。
- 内置命名空间在 Python 解释器启动时创建,会一直保留,不被删除。
- 模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会一直保存到解释器退出。
- 当函数被调用时创建一个局部命名空间,当函数返回结果 或 抛出异常时,被删除。每一个递归调用的函数都拥有自己的命名空间。
命名空间的访问
global
对于不可变数据类型 在局部可是查看全局作用域中的变量
但是不能直接修改
如果想要修改,需要在程序的一开始添加global声明
如果在一个局部(函数)内声明了一个global变量,那么这个变量在局部的所有操作将对全局的变量有效
a = 1
def outer():
a = 1
def inner():
def inner2():
global a
a += 1
print(a)
inner2()
inner()
print('--', a)
outer()
print(a)
在局部空间中声明一个全局 变量,并操作该变量
nonlocal
a = 1
def outer():
a = 1
def inner():
a = 10
print(a)
def inner2():
nonlocal a
a += 1
print(a)
inner2()
print('==',a)
inner()
print('--', a)
outer()
nonlocal 局部命名空间中 内层函数对外层函数中的变量进行修改(作用仅限最近的一层)
1.外部必须有这个变量
2.在内部函数声明nonlocal变量之前不能再出现同名变量
3.内部修改这个变量如果想在外部有这个变量的第一层函数中生效
LEGB
LEGB含义解释:
L-Local(function);函数内的名字空间
E-Enclosing function locals;外部嵌套函数的名字空间(例如closure)
G-Global(module);函数定义所在模块(文件)的名字空间
B-Builtin(Python);Python内置模块的名字空间
前面讲到,Python的命名空间是一个字典,字典内保存了变量名称与对象之间的映射关系,因此,查找变量名就是在命名空间字典中查找键-值对。
Python有多个命名空间,因此,需要有规则来规定,按照怎样的顺序来查找命名空间,LEGB就是用来规定命名空间查找顺序的规则。
LEGB规定了查找一个名称的顺序为:local–>enclosing function locals–>global–>builtin
关于重名函数
首先我们要知道在python中并没有重载的概念(什么是重载:方法名相同,但是参数不同)
对于在命名空间中存在重名函数
def input():
print('input is running now')
input()
input函数是python 中内置的函数,存在于内置命名空间,如果我们自己又定义了一个input函数会怎么样呢?我们定义的input函数存在于全局命名空间
运行结果
input is running now
并没有执行内置的input 而是执行了 我们自行定义的input。
结论
在正常情况下,直接使用内置的名字
当我们在全局定义了和内置名字空间中同名的名字时,会使用全局的名字(当我自己有的时候 我就不找我的上级要了如果自己没有 就找上一级要 上一级没有再找上一级 如果内置的名字空间都没有 就报错
多个函数应该拥有多个独立的局部名字空间,不互相共享
补充:python中为什么没有重载
重载是为了解决静态语言的灵活性,python这种语言需要什么重载
首先我们要知道函数的重载主要是为了解决两个问题:
1、可变参数类型.
2、可变参数个数
另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。
1、首先对于python 变量是没有类型的,他可以接受任意类型。
2、对于可变参数,之前有说过动态参数 *args **kwargs
作用域
全局作用域 —— 作用在全局 —— 内置和全局名字空间中的名字都属于全局作用域 ——globals()
局部作用域 —— 作用在局部 —— 函数(局部名字空间中的名字属于局部作用域) ——locals()
locals()和globals()
def demo():
a = 1
b = 2
def inner():
z =1
print(globals())
print(locals())
inner()
demo()
-------------
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01105590>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/untitled/oldBody/day10/work.py', '__cached__': None, 'demo': <function demo at 0x02D325D0>}
{'z': 1}
locals()是当前环境的命名空间中的内容
globals()是全局命名空间和内置命名空间的内容
作用域链和函数的嵌套
函数的嵌套调用
def max(a,b):
return a if a>b else b
def the_max(x,y,z): #函数的嵌套调用
c = max(x,y)
return max(c,z)
print(the_max(1,2,3))
函数的嵌套定义
函数嵌套就是函数中定义一个函数
def func1():
a = 1
def func2():
print(a)
func2()
func1()
内部函数可以使用外部函数的变量
函数的本质
函数名本质上就是函数的内存地址
可以被引用
def func():
print('in func')
f = func
print(f)
---------------
<function func at 0x00B02588>
可以被当作容器类型的元素
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
l = [f1,f2,f3]
d = {'f1':f1,'f2':f2,'f3':f3}
#调用
l[0]()
d['f2']()
---------------
f1
f2
可以当作函数的参数和返回值
def func1():
print('asd')
def demo(f):
f()
demo(func1)
-------
asd
def func1():
print('asd')
def demo(f):
return f
print(demo(func1))
--------
<function func1 at 0x02BC25D0>
第一类对象(first-class object)指
1.可在运行期创建
2.可用作函数参数或返回值
3.可存入变量的实体。
变量就是第一类对象
闭包
内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数
def demo():
a = 1
def inner():
print(a)
return inner
f = demo() # f 就是 inner函数的内存地址值
f()
这里调用f() 可以读取到a的值 ,是因为 inner是个闭包, 当执行完f = demo()时 a并没有随着这行代码的执行结束而被回收。
我们调用f()时,可以一直使用a的内存而不需要多次创建了
判断是否是闭包
#输出的__closure__有cell元素 :是闭包函数
def func():
name = 'eva'
def inner():
print(name)
print(inner.__closure__)
return inner
f = func()
f()
#输出的__closure__为None :不是闭包函数
name = 'egon'
def func2():
def inner():
print(name)
print(inner.__closure__)
return inner
f2 = func2()
f2()
闭包的嵌套
def wrapper():
money = 1000
def func():
name = 'eva'
def inner():
print(name,money)
return inner
return func
f = wrapper() # 拿到 func 的地址 并赋值给f
i = f() # 执行f() 拿到inner的地址并赋值给i
i() # 执行i
i()
i()
闭包不管嵌套多少层 执行多少次 像上述代码中的 name money 都只创建一次