Loading

Python名称空间与作用域及闭包函数

名称空间

存放名字与值的内存地址对应关系的空间。

名称空间分为三类

内置名称空间:

存放Python解释器内置的名字,如input,print,list等。

存活周期:Python解释器启动则产生,Python解释器关闭则回收,只存在一个。

全局命名空间:

运行顶级代码所定义的名字,或者说不是函数内定义、也不是内置的,剩下的都是全局名称空间。

存活周期:Python文件执行产生,在定义全局名称时会在全局名称空间内将名称放进去,文件执行结束立即回收,只存在一个。

# 直接在一行开头定义一个变量,或者定义一个函数,那么这个变量名与函数名就属于全局名称空间。
x = 10

def func():
    pass

# 流程控制语句缩进内的定义的名称,也是属于全局名称空间。
if 10 > 3:
    a = 111

局部名称空间:

在函数体的运行中开辟的局部的空间,也叫做临时命名空间,同一个函数调用多次会产生多个局部名称空间。

存活周期:函数体执行时产生,函数运行结束,这个空间也会回收。

# 函数内部定义的名称。
def func():  # func本身是全局名称空间。
    x = 10  # x是局部名称空间。

func()

名称空间实际上是相互隔离独立的一个个空间,并没有包含关,嵌套关系仅为了帮助理解。

加载到内存的顺序:

  • 1.内置名称空间

  • 2.全局名称空间

  • 3.临时名称空间

取值顺序:

按照LEGB原则就近取值,取值顺序单向不可逆。

LEGB原则:

  • Local本地 --> Enclosed嵌套函数的外层函数内部 --> Global全局 --> Builtin内置

(从局部开始找时)局部名称空间 --> 全局名称空间 --> 内置名称空间

input = 333
print(input) # 此时是从全局开始取值,input()函数则不会被查找到
# 333

名称空间的嵌套关系是在函数定义阶段,即检测语法时确定的,与函数调用的位置无关,与函数定义位置有关。

函数的取值位置是个很绕的知识点,最好一步一步分析函数的执行步骤,与惯性思维做斗争。举几个例子帮助学习。

示例一:

x = 111  # 1、首先在全局定义x = 111
def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 5、x向全局取值,此时x = 222,所以最终结果为222。

x = 222  # 3、此时全局定义x = 222
func()  # 4、调用func
222


x = 111  # 1、首先在全局定义x = 111
def func():  # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 4、x向全局取值,此时x = 111,所以最终结果为111。

func()  # 3、调用func。
x = 222  # 5、此时在全局定义x = 222,但func函数已经调用,函数内的x已经取值为111。
111

示例二:

x = 1  # 1、在全局定义x = 1
def func():  # 2、定义func函数,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
    print(x)  # 7、打印x,x此时从全局取值,结果为1

def foo():  # 3、定义foo函数
    x = 222  # 5、在foo的局部名称空间内定义x = 222,并不会干扰到全局的x
    func()  # 6、调用func

foo()  # 4、调用foo
1

示例三:

input = 111  # 1、在全局定义input = 111。
def f1():  # 2、定义f1函数。
    def f2():  # 4、在f1局部名称空间内定义f2。
        input = 333  # 7、在f2局部名称空间内定义input = 333。
        print(input) # 8、input先在当前所在名称空间内取值,此时input值为333。
    input = 222  # 5、在f1局部名称空间内定义。
    f2()  # 6、调用f2,执行函数体代码。

f1()  # 3、调用f1,执行f1函数体代码。
333

示例四:

count = 1
def func():
	count = 100
	print(count)  # 直接在当前名称空间取值,count为100。
func()
100

示例五:

def f1():
    m=111
    return m

def f2():
    print(res)  # 此时已确定res取值先从f2局部取值,局部未找到则向全局取值。

def f3():
    print(res)  # 此时已确定res取值先从f3局部取值,局部未找到则向全局取值。

res=f1()  # f1() = m = 111
f2()
f3()

111
111

示例六:

x=111

def f1():
    print(x)  # 定义阶段已经确定先从局部取值,但是局部x是先取值再定义,所以保存
    x = 222

f1()
UnboundLocalError: local variable 'x' referenced before assignment

示例七:

m = [111,]
def func(y):
    m.append(333)
    print(y)

func(m)
m.append(222)
print(m)

[111, 333]
[111, 333, 222]

名称空间不仅是Python独有,这是整个计算机领域的一种设计,在Python之禅的最后一句话。

import this
...
Namespaces are one honking great idea -- let's do more of those!

作用域:

变量的生效范围,分为全局作用域和局部作用域。

全局作用域:

  • 内置名称空间,全局名称空间。全局存活,全局有效。
# x就属于全局名称空间,在全局名称空间和局部名称空间都能访问到。
x = 10
def f1():
    print(x)
print(x)
f1()
10
10

globals()

返回包含当前范围的全局变量的字典。

x = 111
print(globals())

{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000002061940>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/desktop/code.py', '__cached__': None, 'x': 111}

局部作用域:

  • 局部名称空间,临时存活,局部有效。
  • 局部作用域可以引用全局作用域的变量,但不可改变。
x = 10
def f1():
    x = 20

f1()
print(x)

locals()

返回包含当前作用域的局部变量的字典。

x = 111
def func():
    a = 10
    b = 20
    print(locals())
func()
{'a': 10, 'b': 20}

global

只能在局部名称空间使用。在局部声明一个全局作用域的变量。在 global 语句中列出的名称不得在同一代码块内该 global 语句之前的位置中使用。

global 语句中列出的名称不得被定义为正式形参,不也得出现于 for 循环的控制目标、class 定义、函数定义、import 语句或变量标注之中。

局部名称空间不能修改全局名称空间的不可变数据类型的值,只能引用。

c = 1
def func():
    c += 1		# 不可更改,在更改时会从先局部名称空间取c的值,然后再赋值给c,造成先引用、后定义的保错
    print(c)
func()
#  local variable 'count' referenced before assignment

对于可变类型,也不能使用先取值后修改再赋值给原变量名这种方式,而是使用内置方法来修改。

c = [1,2,3]
def func():
    c += [3,4,5]  # 相当于 c = c + [3,4,5]
    print(c)
func()
UnboundLocalError: local variable 'c' referenced before assignment

c = [1,2,3]
def func():
    c.extend([3,4,5])
    print(c)
func()
[1, 2, 3, 3, 4, 5]

使用global关键字,可以在局部修改一个全局变量。

c = 1
def func():
    global c
    c += 1
    print(c)
func()
2

nonlocal

只能在函数嵌套定义的内层函数中使用,在第一层局部名称空间内使用会报错。用作函数嵌套定义中,在内层函数中声明一个外层局部名称空间的变量。

nonlocal 语句中列出的名称不得与之前存在于局部作用域中的绑定相冲突。

x = 111

def f1():
    x = 222

    def f2():
        nonlocal x
        x = 333

    f2()
    print(x)

f1()
333

闭包函数定义:

  • 闭函数:是嵌套在函数中的函数。
  • 包函数:内层闭函数引用外层函数一个非全局的变量,就是闭包。
def f1():
    x = 10
    def f2():
        print(x)

闭包的作用:

被引用的非全局变量也称作自由变量,这个自由变量会与内层函数产生一个绑定关系。
自由变量不会在内存中消失。

1、保证数据安全。函数外无法访问自由变量。

def func1():
    li = []
    def func2(x):
        li.append(x)
        return li
    ruturn func2
f = func1()
print(f(100))   # [100]
print(f(100))   # [100, 100]
print(f(100))   # [100, 100, 100]
#li就是自由变量,并没有消失,但无法从函数外访问。

2、为内部函数传参。

返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

def f1(x):
    def f2():
        print(x,y)  # 优先使用外层的作用域,不会被全局所影响。
    x = 10
    y = 20
    return f2

x = 1
y = 2
f1(x)()
10 20

如何判断一个嵌套函数是不是闭包:

print(func.__code__.co_freevars)
# 只要返回值有自由变量那么就是闭包

def func():
    x = 10
    def f1():
        print(x)
    return f1

print(func().__code__.co_freevars)
('x',)
posted @ 2020-08-03 18:21  吃了好多肉  阅读(312)  评论(0编辑  收藏  举报