python学习_program_Python的命名空间和作用域


楔子

image-20220527181041536

日常的Python开发中,变量的使用是一个简单但是不注意又会出错的地方,本文从变量的命名空间(NameSpace)和作用域(Scope)的角度去讲解他们的含义和与变量的关系。

变量名在Python中的含义

如果您曾经阅读过“ python之禅”(在Python解释器中输入 import this),则会看到最后一行是,

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

翻译过来就是:命名空间是个绝妙的主意,我们应好好利用它。

那么到底什么是命名空间(NameSpace)呢?让我们首先看一下什么叫做变量名(Name)吧。

变量名(Name),或者叫变量标识符(Identifier),简单理解就是某个python对象的名字,而我们知道:Python 中的一切都是对象。变量名是访问底层对象的一种方式。

例如,当我们进行赋值时:a = 22是一个存储在内存中的对象,而a是我们与之关联的名称。我们可以通过内置函数id()获取某个对象在内存中的地址。让我们看看它的使用范例。

a = 2
print('id(2) =', id(2))

print('id(a) =', id(a))

输出:

id(2) = 140728274186016
id(a) = 140728274186016

在这里,两者都指向同一个对象2,因此它们具有相同的id()。让我们让事情变得更有趣一些。

a = 2
print('id(a) =', id(a))

a = a+1
print('id(a) =', id(a))

print('id(3) =', id(3))

b = 2
print('id(b) =', id(b))
print('id(2) =', id(2))

输出:

id(a) = 140728274186016
id(a) = 140728274186048
id(3) = 140728274186048
id(b) = 140728274186016
id(2) = 140728274186016

上述程序的在执行中发生了什么?让我们用一张图来解释:

image-20220530192110203

最初,一个整型对象2被我们创建了,并且用a作为变量名(标识符)与之关联(可以理解为贴标签),当我们这样执行a = a+1时,一个新的整型对象3被我们创建了(先执行左边的语句,创建了3这个对象),此时a与3关联(再执行赋值语句,可以理解为标签从2上面撕下来贴到对象3的上面)。

此时,id(a)和id(3)的值是相同的,他们都指向内存中存放整型对象3的地址。

此外,当执行b=2时,一个新的变量名(标签)与整型对象2相关联(把b这个标签贴到了整型对象2上面)

因为这种方式对于Python来说不用每次赋值都创建一个新的对象(取而代之只是增加或者更换标签),因此是足够高效的。变量名绑定这种动态的特性使得Python更强大。变量名可以指代任何类型的对象。

>>> a = 5
>>> a = 'Hello World!'
>>> a = [1,2,3]

上面的代码中,变量名a分别指代了3种对象(整型,字符串,列表)的实例,并且这些语句都可以成功执行,而在Python中,函数也是一种对象,因此变量名同样可以指代函数。(给函数贴上标签)

def printHello():
    print("Hello")


a = printHello

a()

输出:

Hello

a同样可以指向一个函数,并且我们可以通过变量名a调用这个函数。

在Python中,命名空间是什么呢

既然我们已经理解了变量名(Name)是什么,我们现在可以来讨论命名空间(NameSpace)的概念了。

简单的说,命名空间(NameSpace)就是一组变量名(Name)的集合。

在 Python 中,您可以将命名空间想象为您定义的每个变量名到相应对象的映射集。

不同的命名空间可以在给定时间共存,但是是完全隔离的。

当我们启动 Python 解释器时,会创建一个包含所有内置名称(built-in)的命名空间,并且只要解释器运行,它就存在。

这就是我们始终可以从程序的任何部分使用诸如 id(), print()等内置函数的原因。每个模块(module)都会创建自己的全局命名空间。

这些不同的命名空间是相互隔离的。因此,不同模块中可能存在相同的变量名且不会发生冲突。

每个模块可以定义不同的函数和类。当函数被调用时会创建一个本地命名空间,其中包含了所有在此函数内定义的变量名。类的情况与函数类似。下图可能有助于理解这个概念。

image-20220530195031406

Python中的变量作用域(Scope)

尽管定义了各种独一无二的命名空间,但我们是无法在程序的每个地方都可以访问到全部的命名空间的。这就是作用域的概念。

作用域是程序的一部分,它表示代码在运行时可以直接获取到的命名空间。

在程序运行的任何时刻,至少有三个互相嵌套的作用域:

  1. 当前函数的作用域:对于函数内定义的变量(本地变量名→本地命名空间)
  2. 当前模块的作用域:对于模块内定义的变量名(全局变量名→全局命名空间)
  3. 最外层作用域:内置的变量名(所有预先定义的内置变量名→内置命名空间)

当在函数内部进行变量名的引用时,会在本地命名空间中搜索,然后在全局命名空间中搜索,最后在内置命名空间中搜索。

如果在一个函数内部另有一个函数,则一个新作用域(内部函数)嵌套在本地命名空间内。

Python中命名空间和作用域的例子

a = 10

def outer_function():
    b = 20
    def inner_func():
        c = 30
        

这里,变量a位于全局命名空间中。变量b位于函数outer_function()的本地命名空间中。c位于嵌套函数inner_func内的本地命名空间中。

如果我们在inner_func中尝试给b赋值,一个新的变量名会被创建在本地命名空间中,并且与上层函数中的b并不相同(因为不在同一命名空间内,所以不会指向同一内存地址),当我们尝试给a赋值时同理。

然而,如果我们在内部作用域声明变量名a是全局变量(global),那么所有对a的赋值和引用都会指向这个全局变量a。相似的,如果我们想在inner_func函数里绑定变量名b,使得引用和指代都是outer_function内的b,那么我们必须在inner_func中声明b为非本地变量(nolocal)。下面的例子更清楚的说明了这件事。

def outer_function():
    a = 20

    def inner_function():
        a = 30
        print('a =', a)

    inner_function()
    print('a =', a)


a = 10
outer_function()
print('a =', a)

如您所想,该程序输出如下:

a = 30
a = 20
a = 10

在这个程序中,三个不同变量名a在独立的三个命名空间中被定义并且被顺序的访问。而在下面的程序中:

def outer_function():
    global a
    a = 20

    def inner_function():
        global a
        a = 30
        print('a =', a)

    inner_function()
    print('a =', a)


a = 10
outer_function()
print('a =', a)

输出如下:

a = 30
a = 30
a = 30 

在这里,由于使用了关键字global,因此所有的引用和赋值都是针对全局变量a而进行的。

总结

本文对python中变量的命名空间和作用域进行了讲解和示例 ,希望对你有帮助~

本文大量参照了https://www.programiz.com/python-programming/namespace。

希望大家能有所收获~

posted @ 2023-12-15 18:07  故君子慎为善  阅读(43)  评论(0)    收藏  举报