栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

一网打尽 命名空间 | 作用域 | 闭包 | global | nonlocal | globals | locals

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

一网打尽 命名空间 | 作用域 | 闭包 | global | nonlocal | globals | locals

一网打尽 命名空间 | 作用域 | 闭包 | global | nonlocal | globals | locals

基础概念

命名空间作用域闭包globalnonlocalglobalslocalsdir([object]) 深入理解

为什么命名空间三个作用域却四个?闭包被返回时, 外层函数命名空间已经销毁, 为什么之后还可以使用?谁可以产生作用域?如何理解修改built-in域? 后记

基础概念 命名空间

名称到对象的映射,python中大多数用字典实现

built-in,内建global,全局local,局部

命名空间是有生命周期的, 简单说就是函数返回了就结束了

作用域

python程序可以直接访问命名空间的正文区域

built-in,内建global,全局enclosing,闭包外且非全局的local,局部

读取变量时, 查找顺序肯定是由内向外的,局部没有,就找enclosing,再没有就找global,都没有找内建, LEGB

再没有就NameError, 如果其中一步有了, 那就停止寻找了

x = 'global'
def foo():
    x = 'foo'
    def boo():
        print(x)
    boo()
foo()
# foo

关于修改变量(这里说的是变量指向, 不是说可变对象的那个修改), 只能修改自身作用域范围内的变量, 内层和外层都不能修改

x='global'
def foo():
    x = 'foo'
foo()
print(x)
# global
闭包

函数中定义函数,内层函数用到了外层函数的变量,这时内层函数就是一个闭包

实际上闭包很像对象,但是如果需要函数粒度的抽象,那就用闭包

global

内层作用域直接修改global的变量,无论多内层他都能直接修改global

x='global'
def a():
    x='a'
    def b():
        x='b'
        def c():
            global x
            x = 'c'
            print('c,x',x)
        c()
        print('b,x',x)
    b()
    print('a,x',x)
a()
print('global,x',x)
# 输出
"""c,x c
b,x b
a,x a
global,x c"""
nonlocal

修改enclosing作用域变量的值

def foo():
    x='foo'
    def doo():
        nonlocal x
        x = 'doo'
    doo()
    print(x)
foo()
# doo
globals

返回全局命名空间的映射,是个字典,貌似是可以改的, 经测试改了也有效果

globals()['xxx'] = 10
print(xxx)
# 输出10
locals

返回局部命名空间的映射,是个字典,别修改,改了也没用, 官方是这样说的

def foo():
    locals()['xxx']=10
    print(xxx)
foo()
# 报错 NameError: name 'xxx' is not defined

在模块级别用globals和locals返回同一个字典

dir([object])

首先返回的是个列表

如果有参数, 尝试返回传入对象的有效属性列表如果没参数, 返回当前作用域中的名称列表

也就是可以这样

print(sorted(dir()) == sorted(globals()))
# True

dir试图返回对象最相关的而不是最全面的信息, 主要是用在交互时用, 而不是用来保证结果的一致性

深入理解 为什么命名空间三个作用域却四个?

命名空间和作用域是可以一一对应的, 为什么多出来一个enclosing区域

是因为nonlocal函数的原因, nonlocal可以修改local域外, global域内的, 这个区域其实是外层函数的local域, 为了区分, 将其称为enclosing

变量查找顺序是由内而外的, 如果有多个enclosing域, 也是由内向外, 而且nonlocal只能修改第一个包含该变量的enclosing作用域,例如

x='global'
def a():
    x='a'
    def b():
        x='b'
        def c():
            nonlocal x
            x = 'c'
            print('c,x',x)
        c()
        print('b,x',x)
    b()
    print('a,x',x)
a()
print('global,x',x)
# 输出
"""c,x c
b,x c
a,x a
global,x global"""

c中的nonlocal x只能修改b中的x,a中的x是鞭长莫及的。加入b中没定义x那么c就可以修改a的x,例如

x='global'
def a():
    x='a'
    def b():
        def c():
            nonlocal x
            x = 'c'
            print('c,x',x)
        c()
        print('b,x',x)
    b()
    print('a,x',x)
a()
print('global,x',x)
# 输出
"""c,x c
b,x c
a,x c
global,x global"""
闭包被返回时, 外层函数命名空间已经销毁, 为什么之后还可以使用?
def foo():
    x = 10
    def boo():
        print(x)
    return boo
func = foo()
func()

上面这个典型的闭包例子, 当我们执行func = foo()时, foo的命名空间就消失了, x也没了, 但为什么之后执行func()时仍然可以print(x)?

闭包函数都有闭包属性, 即__closure__属性,发现是个tuple[cell],闭包用到的外层函数的变量,都有会有一个对应一个cell对象,而且cell对象的cell_contents属性,可以直接获取变量的值, 直接看下面代码

def foo():
    x = 10
    def boo():
        print(x)
    return boo
func = foo()
print(func.__closure__)
print(func.__closure__[0].cell_contents)
# (,)
# 10

所以, 虽然外层命名空间消失了, 但是被闭包用到的变量都通过cell对象维持与闭包的联系

每个被闭包用到的变量都有一个对应的cell对象,所以当有两个闭包,用同一个外层变量时,它们__closure__中的cell对象是同一个, 看下面的例子

def foo():
    x = 10
    def boo():
        print(x)
    def coo():
        print(x)
    print(id(boo.__closure__[0]) == id(coo.__closure__[0]))
foo()
# True
谁可以产生作用域?

经常说只有函数|类|模块会产生新的作用域, 代码块if|for|while等不会产生新的作用域, 实际上各种生成器表达式也会产生作用域, 可以试试下面这句

[print(locals()) for a in range(10)]
如何理解修改built-in域?

运行这段代码, 我们会有种错觉, global作用域代码竟然修改了built-in作用域变量!

class Foo:
    def __init__(self, *args, **kwargs): print('Foo')
set = Foo
def foo():
    print(set([1, 2, 2, 3]))
foo()
print(set([2, 2, 2, 2]))
# Foo
# <__main__.Foo object at xxx>
# Foo
# <__main__.Foo object at xxx>

事实并非如此此, 我们只是在global命名空间添加了一个名为set的变量而已, 只是由于变量搜索顺序按照LEGB, 找到一个就停手, 所以当其他作用域使用set时, 搜索到global就得到了, 如果不去添加这个set变量, 平常都要搜索到built-in的, 看下面代码, 两次输出只多了一个set而已

print(locals())
set = 1
print(locals())

如果定义了跟built-in域一样的变量, 可以import builtins模块去使用真正built-in域的变量

import builtins
class Foo:
    def __init__(self, *args, **kwargs): print('Foo')
set = Foo
print(builtins.set([1,2,2,2,3]))
# {1, 2, 3}

如何真正修改built-in域的变量呢? 我想大家应该已经知道了

import builtins
class Foo:
    def __init__(self, *args, **kwargs): print('Foo')
builtins.set = Foo
print(set([1,1,1,1,1,2]))
print(builtins.set([1,2,2,2,3]))
# Foo
# <__main__.Foo object at 0x0000022E111C8FD0>
# Foo
# <__main__.Foo object at 0x0000022E111C8FD0>
后记

本文是在作者搜索闭包时顺带出来的一连串问题, 并根据官方文档和网上一些文章编写的, 不保证都对, 但都是跑了代码测试和经过思考的, 实际上这些函数和问题在日常编码中基本不会遇到, 但要是能梳理清楚了, 对理解Python肯定是有帮助的; 作用域方面的问题暂时只能想这么多, 如果有其他知识, 欢迎补充! 结束.

如有问题, 欢迎交流!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/740707.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号