广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Python学习之名字,作用域,名字空间(下)
  • 794
分享到

Python学习之名字,作用域,名字空间(下)

2024-04-02 19:04:59 794人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录LEGB规则global表达式属性引用与名字引用属性空间小结前言: 这里再回顾一下函数的local空间,首先我们往global空间添加一个键值对相当于定义一个全局变量,那么如果往

前言:

这里再回顾一下函数的local空间,首先我们往global空间添加一个键值对相当于定义一个全局变量,那么如果往函数的local空间里面添加一个键值对,是不是也等价于创建了一个局部变量呢?

def f1():
    locals()["name "] = "夏色祭"
    try:
        print(name)
    except Exception as e:
        print(e)
f1()  # name 'name' is not defined

对于全局变量来讲,变量的创建是通过向字典添加键值对的方式实现的。因为全局变量会一直在变,需要使用字典来动态维护。

但对于函数来讲,内部的变量是通过静态方式存储和访问的,因为局部作用域中存在哪些变量在编译的时候就已经确定了,我们通过PyCodeObject的co_varnames即可获取内部都有哪些变量。

所以,虽然我们说查找是按照LGB的方式查找,但是访问函数内部的变量其实是静态访问的,不过完全可以按照LGB的方式理解。

因此名字空间是python的灵魂,它规定了Python变量的作用域,使得Python对变量的查找变得非常清晰。

LEGB规则

而从Python2.2开始,由于引入了嵌套函数,所以最好的方式应该是内层函数找不到某个变量时先去外层函数找,而不是直接就跑到global空间里面找。

那么此时的规则就是LEGB:

a = 1
def foo():
    a = 2

    def bar():
        print(a)
    return bar
f = foo()
f()
"""
2
"""

调用f,实际上调用的是函数bar,最终输出的结果是2。如果按照LGB的规则来查找的话,由于函数bar的作用域没有a、那么应该到全局里面找,打印的结果是1才对。

但是我们之前说了,作用域仅仅是由文本决定的,函数bar位于函数foo之内,所以函数bar定义的作用域内嵌于函数foo的作用域之内。换句话说,函数foo的作用域是函数bar的作用域的直接外围作用域。

所以应该先从foo的作用域里面找,如果没有那么再去全局里面找。而作用域和名字空间是对应的,所以最终打印了2。

另外在执行f = foo()的时候,会执行函数foo中的def bar():语句,这个时候解释器会将a=2与函数bar捆绑在一起,然后返回,这个捆绑起来的整体就叫做闭包。

所以:闭包 = 内层函数 + 引用的外层作用域

这里显示的规则就是LEGB,其中E表示enclosing,代表直接外围作用域。

global表达式

有一个很奇怪的问题,最开始学习Python的时候,笔者也为此困惑了一段时间,下面来看一下。

a = 1
def foo():
    print(a)
foo()
"""
1
"""

首先这段代码打印1,这显然是没有问题的,不过下面问题来了。

a = 1
def foo():
    print(a)
    a = 2
foo()
"""
UnboundLocalError: local variable 'a' referenced before assignment
"""

仅仅是在print语句后面新建了一个变量a,结果就报错了,提示局部变量a在赋值之前就被引用了,这是怎么一回事,相信肯定有人为此困惑。

而想弄明白这个错误的原因,需要深刻理解两点:

  • 一个赋值语句所定义的变量,在这个赋值语句所在的整个作用域内都是可见的;
  • 函数中的变量是静态存储、静态访问的, 内部有哪些变量在编译的时候就已经确定;

在编译的时候,因为a = 2这条语句,所以知道函数中存在一个局部变量a,那么查找的时候就会在当前作用域中查找。但是还没来得及赋值,就print(a)了,所以报错:局部变量a在赋值之前就被引用了。但如果没有a = 2这条语句则不会报错,因为知道局部作用域中不存在a这个变量,所以会找全局变量a,从而打印1

更有趣的东西隐藏在字节码当中,我们可以通过反汇编来查看一下:

import dis
a = 1
def g():
    print(a)
dis.dis(g)
"""
  7           0 LOAD_GLOBAL              0 (print)
              2 LOAD_GLOBAL              1 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
"""

def f():
    print(a)
    a = 2
dis.dis(f)
"""
 12           0 LOAD_GLOBAL              0 (print)
              2 LOAD_FAST                0 (a)
              4 CALL_FUNCTION            1
              6 POP_TOP

 13           8 LOAD_CONST               1 (2)
             10 STORE_FAST               0 (a)
             12 LOAD_CONST               0 (None)
             14 RETURN_VALUE
"""

中间的序号代表字节码的偏移量,我们先看第二条,g的字节码是LOAD_GLOBAL,意思是在global名字空间中查找;而f的字节码是LOAD_FAST,表示在local名字空间中查找。因此结果说明Python采用了静态作用域策略,在编译的时候就已经知道了名字藏身于何处。

而且上面的例子也表明,一旦函数内有了对某个名字的赋值操作,这个名字就会在作用域内可见,就会出现在local名字空间中。换句话说,会遮蔽外层作用域中相同的名字。

当然Python也为我们精心准备了global关键字,让我们在函数内部修改全局变量。比如函数内部出现了global a,就表示我后面的a是全局的,直接到global名字空间里面去找,不要在local空间里面找了。

a = 1
def bar():
    def foo():
        global a
        a = 2
    return foo

bar()()
print(a)  # 2
# 当然,也可以通过globals函数拿到名字空间
# 然后直接修改里面的键值对

但如果外层函数里面也出现了变量a,而我们想修改的也是外层函数的a、不是全局的a,这时该怎么办呢?Python同样为我们准备了关键字: nonlocal,但是使用nonlocal的时候,必须是在内层函数里面。

a = 1
def bar():
    a = 2
    def foo():
        nonlocal a
        a = "xxx"
    return foo

bar()()
print(a)  # 1
# 外界依旧是1,但是bar里面的a已经被修改了

属性引用与名字引用

属性引用实质上也是一种名字引用,其本质都是到名字空间中去查找一个名字所引用的对象。这个就比较简单了,比如a.xxx,就是到a里面去找属性xxx,这个规则是不受LEGB作用域限制的,就是到a里面查找,有就是有、没有就是没有。

但是有一点需要注意,我们说查找会按照LEGB规则,但这必须限制在自身所在的模块内,如果是多个模块就不行了。举个栗子:

# a.py
print(name)
# b.py
name = "夏色祭"
import a

关于模块的导入我们后续会详细说,总之目前在b.py里面执行的import a,你可以简单认为就是把a.py里面的内容拿过来执行一遍即可,所以这里相当于print(name)。

但是执行b.py的时候会提示变量name没有被定义,可把a导进来的话,就相当于print(name),而我们上面也定义name这个变量了呀。

显然,即使我们把a导入了进来,但是a.py里面的内容依旧是处于一个模块里面。而我们也说了,名称引用虽然是LEGB规则,但是无论如何都无法越过自身所在的模块。print(name)在a.py里面,而变量name被定义在b.py里面,所以不可能跨过模块a的作用域去访问模块b里面的name,因此在执行 import a 的时候会抛出 NameError。

所以我们发现,虽然每个模块内部的作用域规则有点复杂,因为要遵循LEGB;但模块与模块之间的作用域还是划分的很清晰的,就是相互独立。

关于模块,我们后续会详细说。总之通过 . 的方式,本质上都是去指定的名字空间中查找对应的属性。

属性空间

我们知道,自定义的类里面如果没有__slots__,那么这个类的实例对象都会有一个属性字典。

class Girl:
    def __init__(self):
        self.name = "古明地觉"
        self.age = 16
g = Girl()
print(g.__dict__)  # {'name': '古明地觉', 'age': 16}

# 对于查找属性而言, 也是去属性字典中查找
print(g.name, g.__dict__["name"])  # 古明地觉 古明地觉

# 同理设置属性, 也是更改对应的属性字典
g.__dict__["gender"] = "female"
print(g.gender)  # female

当然模块也有属性字典,本质上和类的实例对象是一致的。

import builtins
print(builtins.str)  # <class 'str'>
print(builtins.__dict__["str"])  # <class 'str'>
# 另外,有一个内置的变量 __builtins__,和导入的 builtins 等价
print(__builtins__ is builtins)  # True

另外这个__builtins__位于 global名字空间里面,然后获取global名字空间的globals又是一个内置函数,于是一个神奇的事情就出现了。

print(globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"]
      )  # <module 'builtins' (built-in)>
print(globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].
      globals()["__builtins__"].globals()["__builtins__"].list("abc")
      )  # ['a', 'b', 'c']

所以global名字空间和builtin名字空间,都保存了指向彼此的指针,不管套娃多少次,都是可以的。

小结

在 Python 中,一个名字(变量)的可见范围由作用域决定,而作用域由语法静态划分,划分规则提炼如下:

  • .py文件(模块)最外层为全局作用域;
  • 遇到函数定义,函数体形成子作用域;
  • 遇到类定义,类定义体形成子作用域;
  • 名字仅在其作用域以内可见;
  • 全局作用域对其他所有作用域可见;
  • 函数作用域对其直接子作用域可见,并且可以传递(闭包);

与作用域相对应, Python在运行时借助PyDictObject对象保存作用域中的名字,构成动态的名字空间 。

这样的名字空间总共有 4 个:

  • 局部名字空间(local):不同的函数,局部名字空间不同,可以通过调用 locals 获取;
  • 全局名字空间(global):全局唯一,可以通过调用 globals 获取;
  • 闭包名字空间(enclosing)
  • 内置名字空间(builtin):可以通过调用 builtins__.__dict 获取;

查找名字时会按照LEGB规则查找,但是注意:无法跨越文件本身,也就是按照自身文件的LEGB。如果属性查找都找到builtin空间了,那么证明这已经是最后的倔强。如果builtin空间再找不到,那么就只能报错了,不可能跑到其它文件中找。

到此这篇关于Python学习之名字,作用域,名字空间(下)的文章就介绍到这了,更多相关Python名字空间内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Python学习之名字,作用域,名字空间(下)

本文链接: https://www.lsjlt.com/news/117929.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Python学习之名字,作用域,名字空间(下)
    目录LEGB规则global表达式属性引用与名字引用属性空间小结前言: 这里再回顾一下函数的local空间,首先我们往global空间添加一个键值对相当于定义一个全局变量,那么如果往...
    99+
    2022-11-11
  • Python学习之名字,作用域,名字空间
    目录变量只是一个名字作用域和名字空间LGB规则eval和exec前言: 我们在PyFrameObject里面看到了3个独立的名字空间:f_locals、f_globals、f_bui...
    99+
    2022-11-11
  • Python作用域与名字空间源码学习笔记
    目录作用域与名字空间1. 名字绑定1.1 赋值1.2 模块导入1.3 函数、类定义1.4 as关键字2. 作用域2.1 静态作用域2.2 划分作用域2.3 闭包作用域2.4 类作用域...
    99+
    2022-11-12
  • 解密Python中的作用域与名字空间
    目录楔子变量只是一个符号变量的可见性LGB 规则局部变量是静态存储的LEGB 规则global 表达式属性空间小结楔子 前面我们介绍了栈帧,在里面看到了 3 个独立的名字空间:f_l...
    99+
    2023-02-28
    Python作用域 名字空间 Python作用域 Python 名字空间
  • Python中的作用域与名字空间实例分析
    这篇文章主要介绍“Python中的作用域与名字空间实例分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python中的作用域与名字空间实例分析”文章能帮助大家解决问题。变量只是一个符号从解释器的角...
    99+
    2023-07-05
  • Python名称空间与作用域
    目录一 名称空间1.1 内建名称空间1.2 全局名称空间1.3 局部名称空间二 作用域2.1 全局作用域与局部作用域2.2 作用域与名字查找的优先级一 名称空间 名称空间即存放名字与...
    99+
    2022-11-10
  • Python 作用域和命名空间
    在介绍类之前,我首先要告诉你一些Python的作用域规则。类定义对命名空间有一些巧妙的技巧,你需要知道作用域和命名空间如何工作才能完全理解正在发生的事情。顺便说一下,关于这个主题的知识对任何高级Python程序员都很有用。 让我们从一些定...
    99+
    2023-01-31
    作用 空间 Python
  • Python基础教程之名称空间以及作用域
    目录前言名称空间什么是名称空间名称空间的意义名称空间的查找顺序局部名称空间详解嵌套函数中的查找顺序关于嵌套函数的使用作用域什么是作用域global语句nonlocal语句题目题目小结...
    99+
    2022-11-12
  • python名称空间与作用域详情
    目录一、名称空间1.1 内置名称空间1.2 全局名称空间1.3 局部名称空间1.4 加载顺序1.5 查找顺序二、作用域2.1 全局作用域2.2 局部作用域2.4 函数对象+作用域应用...
    99+
    2022-11-12
  • Python名称空间及作用域怎么用
    今天小编给大家分享一下Python名称空间及作用域怎么用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。名称空间什么是名称空间...
    99+
    2023-06-26
  • Python中的命名空间、作用域以及lo
          最近一直看一本python经典教材——《Python学习手册》,因为之前都是突击学的,也没有仔细看一些经典教材,所以感觉自己的基础掌握的还不是很好,虽然网络上资源多,但我觉得还是有必要买本教材来认真的读一读,底层基础决定上层建筑...
    99+
    2023-01-31
    作用 空间 Python
  • Python名称空间与作用域实例分析
    本篇内容主要讲解“Python名称空间与作用域实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python名称空间与作用域实例分析”吧!一 名称空间名称空间即存放名字与对象映射/绑定关系的...
    99+
    2023-06-30
  • Python函数命名空间和作用域(Local与Global)
    目录1.Local作用域2.Enclosing function locals作用域3.Global全局变量4.非局部变量nonlocal5.就近原则6.函数执行顺序7.循环、判断代...
    99+
    2022-11-13
  • Python函数命名空间和作用域是什么
    这篇文章主要介绍Python函数命名空间和作用域是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.Local作用域a = 100def func5():  &n...
    99+
    2023-06-29
  • 解读Python编程中的命名空间与作用域
    变量是拥有匹配对象的名字(标识符)。命名空间是一个包含了变量名称们(键)和它们各自相应的对象们(值)的字典。 一个Python表达式可以访问局部命名空间和全局命名空间里的变量。如果一个局部变量和一个全局变量...
    99+
    2022-06-04
    作用 空间 Python
  • Python函数命名空间,作用域LEGB及Global详析
    目录一、命名空间和作用域1.1 定义1.2 内建命名空间和内建作用域1.3 全局命名空间和全局作用域1.3 局部命名空间和局部作用域1.4 总结1.5 扩展LEGB二、Global关...
    99+
    2022-11-11
  • Python函数高级(命名空间、作用域、装饰器)
    目录一、名称空间和作用域1、命名空间(Namespace)1、一般有三种命名空间:2、命名空间查找顺序:3、命名空间的生命周期:2、作用域:3、全局变量和局部变量4、函数对象+作用域...
    99+
    2022-11-11
  • Python命名空间与作用域深入全面详解
    目录1.命名空间2.作用域全局变量和局部变量global 和 nonlocal关键字1.命名空间 先看看官方文档的一段话: 命名空间(Namespace)是从名称到对象的映射,大部...
    99+
    2022-11-13
    Python作用域 Python命名空间
  • Python命名空间、作用域和装饰器怎么使用
    今天小编给大家分享一下Python命名空间、作用域和装饰器怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。一、名称空间...
    99+
    2023-06-30
  • Python学习之字符串常用操作详解
    目录1、查找字符串2、分割字符串3、连接字符串4、替换字符串5、移除字符串的首尾字符6、转换字符串的大小写7、检测字符串(后续还会更新)1、查找字符串 除了使用index()方法在字...
    99+
    2022-11-11
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作