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

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

2024-04-02 19:04:59 672人浏览 八月长安

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

摘要

目录变量只是一个名字作用域和名字空间LGB规则eval和exec前言: 我们在PyFrameObject里面看到了3个独立的名字空间:f_locals、f_globals、f_bui

前言:

我们在PyFrameObject里面看到了3个独立的名字空间:f_locals、f_globals、f_builtins。名字空间对于python来说是一个非常重要的概念,Python虚拟机的运行机制和名字空间有着非常紧密的联系。并且在Python中,与名字空间这个概念紧密联系在一起的还有名字、作用域这些概念,下面就来剖析这些概念是如何体现的。

变量只是一个名字

很早的时候我们就说过,从解释器的角度来看,变量只是一个泛型指针PyObject *,而从Python的角度来看,变量只是一个名字、或者说符号,用于和对象进行绑定的。

变量的定义本质上就是建立名字和对象之间的约束关系,所以a = 1这个赋值语句本质上就是将a和1绑定起来,让我们通过a这个符号可以找到对应的PyLonGobject。

除了变量赋值,创建函数、类也相当于定义变量,或者说完成名字和对象之间的绑定。

def foo(): pass
class A(): pass

创建一个函数也相当于定义一个变量,会先根据函数体创建一个函数对象,然后将名字foo和函数对象绑定起来。所以函数名和函数体之间是分离的,同理类也是如此。

import os

导入一个模块,也是在定义一个变量。import os相当于将名字os和模块对象绑定起来,通过os可以找到指定的模块对象。

import numpy as np当中的as语句同样是在定义变量,将名字np和对应的模块对象绑定起来,以后就可以通过np这个名字去获取指定的模块了。

另外,当我们导入一个模块的时候,解释器是这么做的。比如:import os等价于os=__import__("os"),可以看到本质上还是一个赋值语句。

作用域和名字空间

我们说赋值语句、函数定义、类定义、模块导入,本质上只是完成了名字和对象之间的绑定。而从概念上讲,我们实际上得到了一个name和obj之间的映射关系,通过name可以获取对应的obj,而它们的容身之所就是名字空间。

所以名字空间是通过PyDictObject对象实现的,这对于映射来说简直再适合不过了。而在前面介绍字典的时候,我们说过字典是被高度优化的,原因就是虚拟机本身也重度依赖字典,从这里的名字空间即可得到体现。

但是一个模块内部,名字还存在可见性的问题,比如:

a = 1
def foo():
    a = 2
    print(a)  # 2

foo()
print(a)  # 1

我们看到同一个变量名,打印的确是不同的值,说明指向了不同的对象,换句话说这两个变量是在不同的名字空间中被创建的。

然后我们知道名字空间本质上是一个字典,如果两者是在同一个名字空间,那么由于字典的key的不重复性,当执行a=2的时候,会把字典里面key为a的value给更新成2。但是在外面还是打印1,这说明两者所在的不是同一个名字空间,打印的也就自然不是同一个a。

因此对于一个模块而言,内部是可能存在多个名字空间的,每一个名字空间都与一个作用域相对应。作用域就可以理解为一段程序的正文区域,在这个区域里面定义的变量是有意义的,然而一旦出了这个区域,就无效了。

对于作用域这个概念,至关重要的是要记住:它仅仅是由源程序的文本所决定的。在Python中,一个变量在某个位置是否起作用,是由它的文本位置决定的。

因此Python具有静态作用域(词法作用域),而名字空间则是作用域的动态体现,一个由程序文本定义的作用域在Python运行时会转化为一个名字空间、即一个PyDictObject对象。而进入一个函数,显然进入了一个新的作用域,因此函数在执行时,会创建一个名字空间。

我们之前说,在对Python源代码进行编译的时候,对于代码中的每一个block,都会创建一个PyCodeObject与之对应。而当进入一个新的名字空间、或者说作用域时,我们就算是进入一个新的block了。

而根据我们使用Python的经验,显然函数、类都是一个新的block,当Python运行的时候会为它们创建各自的名字空间。

所以名字空间是名字、或者变量的上下文环境,名字的含义取决于名字空间。更具体的说,一个变量绑定的对象是不确定的,需要由名字空间来决定。

位于同一个作用域的代码可以直接访问作用域中出现的名字,即所谓的直接访问;但不同作用域,则需要通过访问修饰符 . 进行属性访问。

class A:
    a = 1


class B:
    b = 2
    print(A.a)  # 1
    print(b)  # 2

如果想在B里面访问A里面的内容,要通过A.属性的方式,表示通过A来获取A里面的属性。但是访问B的内容就不需要了,因为都是在同一个作用域,所以直接访问即可。

访问名字这样的行为被称为名字引用,名字引用的规则决定了Python程序的行为。

a = 1
def foo():
    a = 2
    print(a)  # 2

foo()
print(a)  # 1

还是上面的代码,如果我们把函数里面的a=2给删掉,意味着函数的作用域里面已经没有a这个变量了,那么再执行程序会有什么后果呢?从Python层面来看,显然是会寻找外部的a。

因此我们可以得到如下结论:

  • 作用域是层层嵌套的;
  • 内层作用域可以访问外层作用域;
  • 外层作用域无法访问内层作用域,尽管我们没有试,但是想都不用想。如果是把外层的a=1给去掉,那么最后面的print(a)铁定报错;
  • 查找元素会依次从当前作用域向外查找,也就是查找元素时,对应的作用域是按照从小往大、从里往外的方向前进的;

LGB规则

我们说函数、类有自己的作用域,但是模块对应的源文件本身也有相应的作用域。比如:

name = "编程学习网"
age = 16

def foo():
    return 123

class A:
    pass

由于这个文件本身也有自己的作用域,显然是global作用域,所以解释器在运行这个文件的时候,也会为其创建一个名字空间,而这个名字空间就是global名字空间。它里面的变量是全局的,或者说是模块级别的,在当前文件的任意位置都可以直接访问。

而函数也有作用域,这个作用域称为local作用域,对应local名字空间;同时Python自身还定义了一个最顶层的作用域,也就是builtin作用域,像内置函数、内建对象都在builtin里面。

这三个作用域在Python2.2之前就存在了,所以那时候Python的作用域规则被称之为LGB规则:名字引用动作沿着local作用域(local名字空间)、global作用域(global名字空间)、builtin作用域(builtin名字空间)来查找对应的变量。

而获取名字空间,Python也提供了相应的内置函数:

  • locals函数:获取当前作用域的local名字空间,local名字空间也称为局部名字空间;
  • globals函数:获取当前作用域的global名字空间,global名字空间也称为全局名字空间;
  • __builtins__函数:或者import builtins,获取当前作用域的builtin名字空间,builtint名字空间也称为内置名字空间;

每个函数都有自己local名字空间,因为不同的函数对应不同的作用域,但是global名字空间则是全局唯一。

name = "编程学习网"

def foo():
    pass

print(globals())  
# {..., 'name': '编程学习网', 'foo': <function foo at 0x000002977EDF61F0>}

里面的...表示省略了一部分输出,我们看到创建的全局变量就在里面。而且foo也是一个变量,它指向一个函数对象。

但是注意,我们说foo也是一个独立的block,因此它会对应一个PyCodeObject。但是在解释到def foo的时候,会根据这个PyCodeObject对象创建一个PyFunctionObject对象,然后将foo和这个函数对象绑定起来。

当我们调用foo的时候,再根据PyFunctionObject对象创建PyFrameObject对象、然后执行,这些留在介绍函数的时候再细说。总之,我们看到foo也是一个全局变量,全局变量都在global名字空间中。

总之,global名字空间全局唯一,它是程序运行时的全局变量和与之绑定的对象的容身之所,你在任何一个地方都可以访问到global名字空间。正如,你在任何一个地方都可以访问相应的全局变量一样。

此外,我们说名字空间是一个字典,变量和对象会以键值对的形式存在里面。那么换句话说,如果我手动地往这个global名字空间里面添加一个键值对,是不是也等价于定义一个全局变量呢?

globals()["name"] = "编程学习网"
print(name)  # 编程学习网


def f1():
    def f2():
        def f3():
            globals()["age"] = 16
        return f3
    return f2


f1()()()
print(age)  # 16

我们看到确实如此,通过往global名字空间里面插入一个键值对完全等价于定义一个全局变量。并且global名字空间是唯一的,你在任何地方调用globals()得到的都是global名字空间,正如你在任何地方都可以访问到全局变量一样。

所以即使是在函数中向global名字空间中插入一个键值对,也等价于定义一个全局变量、并和对象绑定起来。

  • name="xxx" 等价于 globals["name"]="xxx";
  • print(name) 等价于 print(globals["name"]);

对于local名字空间来说,它也对应一个字典,显然这个字典就不是全局唯一的了,每一个局部作用域都会对应自身的local名字空间。

def f():
    name = "夏色祭"
    age = 16
    return locals()


def g():
    name = "神乐mea"
    age = 38
    return locals()


print(locals() == globals())  # True
print(f())  # {'name': '夏色祭', 'age': 16}
print(g())  # {'name': '神乐mea', 'age': 38}

显然对于模块来讲,它的local名字空间和global名字空间是一样的,也就是说,模块对应的PyFrameObject对象里面的f_locals和f_globals指向的是同一个PyDictObject对象。

但是对于函数而言,局部名字空间和全局名字空间就不一样了。调用locals是获取自身的局部名字空间,而不同函数的local名字空间是不同的。但是globals函数的调用结果是一样的,获取的都是global名字空间,这也符合函数内找不到某个变量的时候会去找全局变量这一结论。

所以我们说在函数里面查找一个变量,查找不到的话会找全局变量,全局变量再没有会查找内置变量。本质上就是按照自身的local空间、外层的global空间、内置的builtin空间的顺序进行查找。

因此local空间会有很多个,因为每一个函数或者类都有自己的局部作用域,这个局部作用域就可以称之为该函数的local空间;但是global空间则全局唯一,因为该字典存储的是全局变量。无论你在什么地方,通过调用globals函数拿到的永远是全局名字空间,向该空间中添加键值对,等价于创建全局变量。

对于builtin名字空间,它也是一个字典。当local空间、global空间都没有的时候,会去builtin空间查找。问题来了,builtin名字空间如何获取呢?答案是使用builtins模块,通过builtins.__dict__即可拿到builtin名字空间。

# 等价于__builtins__
import builtins

#我们调用list显然是从内置作用域、也就是builtin名字空间中查找的
#但我们只写list也是可以的
#因为local空间、global空间没有的话,最终会从builtin空间中查找
#但如果是builtins.list,那么就不兜圈子了
#表示: "builtin空间,就从你这获取了"
print(builtins.list is list)  # True

builtins.dict = 123
#将builtin空间的dict改成123
#那么此时获取的dict就是123
#因为是从内置作用域中获取的
print(dict + 456)  # 579

str = 123
#如果是str = 123,等价于创建全局变量str = 123
#显然影响的是global空间
print(str)  # 123
# 但是此时不影响builtin空间
print(builtins.str)  # <class 'str'>

这里提一下Python2当中,while 1比while True要快,为什么?

因为True在Python2中不是关键字,所以它是可以作为变量名的。那么Python在执行的时候就要先看local空间和global空间里有没有True这个变量,有的话使用我们定义的,没有的话再使用内置的True。

而1是一个常量,直接加载就可以,所以while True多了符号查找这一过程。但是在python3中两者就等价了,因为True在Python3中是一个关键字,也会直接作为一个常量来加载。

eval和exec

记得之前介绍 eval 和 exec 的时候,我们说这两个函数里面还可以接收第二个参数和第三个参数,它们分别表示global名字空间、local名字空间。

# 如果不指定,默认当前所在的名字空间
# 显然此时是全局名字空间
exec("name = '古明地觉'")
print(name)  # 古明地觉

# 但是我们也可以指定某个名字空间
dct = {}
# 将 dct 作为全局名字空间
# 这里我们没有指定第三个参数,也就是局部名字空间
# 如果指定了全局名字空间、但没有指定局部名字空间
# 那么局部名字空间默认和全局名字空间保持一致
exec("name = 'satori'", dct)
print(dct["name"])  # satori

至于 eval 也是同理:

dct = {"seq": [1, 2, 3, 4, 5]}
try:
    print(eval("sum(seq)"))
except NameError as e:
    print(e)  # name 'seq' is not defined

# 告诉我们 seq 没有被定义
# 因为我们需要将 dct 作为名字空间
print(eval("sum(seq)", dct))  # 15

所以名字空间本质上就是一个字典,所谓的变量不过是字典里面的一个 key。为了进一步加深印象,

再举个模块的例子:

# 我们自定义一个模块吧
# 首先模块也是一个对象,类型为 <class 'module'>
# 但是底层没有将这个类暴露给我们,所以需要换一种方式获取
import sys
module = type(sys)
# 以上就拿到了模块的类型对象,调用即可得到模块对象
my_module = module("自己定义的")
print(sys)  # <module 'sys' (built-in)>
print(my_module)  # <module '自己定义的'>

# 此时的 my_module 啥也没有,我们为其添砖加瓦
my_module.__dict__["name"] = "古明地觉"
print(my_module.name)  # 古明地觉
# 给模块设置属性,本质上也是操作相应的属性字典
# 当然获取属性也是如此。如果再和exec结合的话
code = """
age = 16
def foo():
    return "我是函数foo"
    
from functools import reduce     
"""
# 此时属性就设置在了模块的属性字典里面
exec(code, my_module.__dict__)
print(my_module.age)  # 16
print(my_module.foo())  # 我是函数foo
print(my_module.reduce(int.__add__, [1, 2, 3, 4, 5]))  # 15

怎么样,是不是很有趣呢?以上就是本次分享的所有内容,想要了解更多欢迎前往公众号:Python编程学习圈,每日干货分享

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

--结束END--

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

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

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

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

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

下载Word文档
猜你喜欢
  • Python学习之名字,作用域,名字空间
    目录变量只是一个名字作用域和名字空间LGB规则eval和exec前言: 我们在PyFrameObject里面看到了3个独立的名字空间:f_locals、f_globals、f_bui...
    99+
    2022-11-11
  • Python学习之名字,作用域,名字空间(下)
    目录LEGB规则global表达式属性引用与名字引用属性空间小结前言: 这里再回顾一下函数的local空间,首先我们往global空间添加一个键值对相当于定义一个全局变量,那么如果往...
    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
  • C++学习之命名空间详解
    目录1.命名空间的定义和使用2.命名空间嵌套3.命名空间别名4.标准命名空间总结C++中,命名空间(namespace)是一个重要的概念。命名空间可以为函数、变量、类等定义作用域,以...
    99+
    2023-05-18
    C++命名空间定义 C++命名空间使用 C++命名空间
  • Python名称空间与作用域
    目录一 名称空间1.1 内建名称空间1.2 全局名称空间1.3 局部名称空间二 作用域2.1 全局作用域与局部作用域2.2 作用域与名字查找的优先级一 名称空间 名称空间即存放名字与...
    99+
    2022-11-10
  • Python 作用域和命名空间
    在介绍类之前,我首先要告诉你一些Python的作用域规则。类定义对命名空间有一些巧妙的技巧,你需要知道作用域和命名空间如何工作才能完全理解正在发生的事情。顺便说一下,关于这个主题的知识对任何高级Python程序员都很有用。 让我们从一些定...
    99+
    2023-01-31
    作用 空间 Python
  • 命名空间和作用域
    一、命名空间 一、命名空间的分类: 1、内置命名空间——python解释器: python解释器启动就可以使用的名字存储在内置内存空间; 内置的名字在启动解释器的时候就被加载进内存,如input()、print()等等。 2、全局命名空间...
    99+
    2023-01-31
    作用 空间
  • 名称空间与作用域
    目录 名称空间(掌握) 内置名称空间 全局名称空间 局部名称空间 加载顺序 查找顺序 ...
    99+
    2023-01-31
    作用 名称 空间
  • 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
  • C++中如何使用名字空间
    本篇文章为大家展示了C++中如何使用名字空间,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。C++名字空间更多是用来避免类名的冲突,这在小的项目中可能看不出来,因为头文件和源文件比较少,类名冲突的概率...
    99+
    2023-06-17
  • Python中的命名空间、作用域以及lo
          最近一直看一本python经典教材——《Python学习手册》,因为之前都是突击学的,也没有仔细看一些经典教材,所以感觉自己的基础掌握的还不是很好,虽然网络上资源多,但我觉得还是有必要买本教材来认真的读一读,底层基础决定上层建筑...
    99+
    2023-01-31
    作用 空间 Python
  • Python名称空间与作用域实例分析
    本篇内容主要讲解“Python名称空间与作用域实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python名称空间与作用域实例分析”吧!一 名称空间名称空间即存放名字与对象映射/绑定关系的...
    99+
    2023-06-30
  • 由一个例子到python的名字空间
    源自我的博客 python里面最核心的内容就是:名字空间(namespace) 例1 #!/usr/bin/env python # encoding: utf-8 def func1(): x = 1 prin...
    99+
    2023-01-31
    例子 名字 空间
  • 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
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作