加入收藏 | 设为首页 | 会员中心 | 我要投稿 航空爱好网 (https://www.52kongjun.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

Python中的名实关系——名字、命名空间、作用域

发布时间:2022-11-16 21:00:31 所属栏目:PHP教程 来源:转载
导读: 对象(object)
一切皆对象。
在Python中,包括数据和处理数据的函数,一切实体都是对象。在一个程序的运行过程中,不断地动态创建对象,然后通过对象之间的相互作用修改、销毁已存在的对象

对象(object)

一切皆对象。

在Python中,包括数据和处理数据的函数,一切实体都是对象。在一个程序的运行过程中,不断地动态创建对象,然后通过对象之间的相互作用修改、销毁已存在的对象或生成新的对象。这些对象之间或通过归属相互联系,或通过接口相互作用。

PHP命名空间_空间相册的命名_php命名空间的使用

Python对象的层次结构

Python通过创建对象和对象间的相互作用来完成特定的任务。因此,我们需要在程序中访问对象并通过指令操控对象间的相互作用。那么我们就需要一种方式去引用创建在内存中的Python对象。方式就是为对象命名,称为名字绑定(name binding)。通过对名字求值返回其引用的对象,称为名字解析(name resolution)。

PHP命名空间_空间相册的命名_php命名空间的使用

通过名字引用对象名字(name)

在一个程序中,要保持并访问某个的对象,需要为对象命名,并通过名字访问对象。所谓命名,就是把对象和某个名字进行绑定(binding),绑定确定了名字与实体的对应。

如果在程序中创建一个对象,但是没有为其命名,会导致这个对象无法被使用,因为没有任何方式可以访问它。一个无法引用的对象没有存在的意义,所以Python解释器会自动销毁这个对象。

下面通过几个例子说明名字绑定。

name = 'Bob Smith'
def add(a, b):
    return a + b
class Point(object)
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return "{}({}, {})".format(self.__class__.__name__, self.x, self.y)
    def dist_from_zero(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
a = Point(3, 4)
import math

上面这些例子中,创建了不同类型的对象,并分别为每个对象命名。

其中,赋值语句(assignment statement)是名字绑定的最简单的情况。如,name = 'Bob Smith',其中“=”为赋值操作符(assignment operator),“=”右边的表达式返回一个对象,并与“=”左边的名字进行绑定。

本质上,定义函数、定义类、导入模块等操作和简单的赋值语句没有任何区别,它们做的事情都是创建对象,并和一个名字进行绑定:

但名字不是引用对象的唯一方式,更一般的术语称为对象引用(object reference)。名字是对象引用的一种。

mobile = ["apple", "huawei", "xiaomi"]

在这个例子中,创建了一个列表对象,并用名字mobile引用它。但是列表包含的3个字符串并没有名字,但却可通过 mobile[0], mobile[1], mobile[2] 分别去引用它们。"apple"虽然没有名字,但mobile[0]是它的对象引用。可以认为对象引用是广义的名字。

a = 1
b = [a, 2]

一个对象可以有多个对象引用。上面的例子中,可以通过名字a去访问对象1,也可以通过b[0]去访问。

但是这却要求顶层的容器(collection)对象必须有名字。所以,归根到底还是名字的问题。

作为支持动态类型的面向对象编程语言,理解名字是更好地理解Python的基础。在很多技术文章中,经常使用变量和变量名来阐述名字和实体的关系,变量是指实体还是指实体的名字,往往容易混淆。我个人的理解,变量应该指实体,变量名指该变量的名字。但这要求实体是可变的。比如在C语言中的如下代码:

int a;
a = 1;
a = 2;

int a 声明一个整型变量,变量名为a,并为该变量开辟一个内存空间,后续的赋值操作执行的是原位修改。

而在Python中,事情非常不同,Python是动态类型语言,名字只是名字。因此我使用名字而不是变量或变量名来阐述名字绑定和名字解析。当然这取决于个人的偏好。

命名空间(namespace)

命名空间,或名字空间,记录了名字和对象之间的绑定关系,命名空间能够将名字映射到对象。赋值语句(包括函数和类的定义等)用来创建对象并为对象命名,Python解释器使用命名空间来维护名字与对象的绑定关系。

在程序运行的特定时刻,并非所有的名字都位于同一个命名空间中,事实上,名字分散在相互独立的不同命名空间中。一个名字到底属于哪个命名空间,是由名字定义的位置决定的;对名字求值(名字解析),会根据特定的顺序去查找各个命名空间。

全局命名空间(global namespace)

当运行一个Python文件时,会创建一个全局命名空间,其中包含了该模块文件中创建的所有名字。也就是说,在模块文件顶层定义的名字属于全局命名空间。

如下例所示:

# file: test1.py
a = 3
b = 4
c = a + b
c = "haha"

程序的执行分为如下步骤:

执行模块文件test1.py时,解释器首先创建一个空的全局命名空间,执行第一条语句会创建一个整型对象3,并与名字a绑定,名字a被加入到全局命名空间中。接着又创建一个整型对象4,并于名字b绑定,名字b也被加入到全局命名空间中。随后python执行赋值语句c = a + b 时,会先对表达式a + b求值,这是一个复合表达式,python会先对名字a和b求值,这时python会到全局命名空间中查找名字a和b并返回与它们对应的对象,即3和4。然后3 + 4创建了一个新的整型对象7,并与名字c进行绑定,全局命名空间再次更新,此时其中包含a, b, c三个名字。接着,创建了一个字符串对象"haha",并与名字c进行绑定。这时全局命名空间中仍然只有三个名字,但是名字 c 已经和对象7的绑定解除,转而和对象“haha”绑定在一起。对象7再也无法访问,将被解释器销毁。这种情况属于命名冲突,如果这不是有意为之,那么就要额外注意。

通过上面这个例子,可以看到两个事实:

随着程序的进行,名字的定义会动态更新命名空间。对名字求值,就是到命名空间中查找并返回与之绑定的对象。

内置命名空间(Built-in namespace)

在Python中有一个天然存在的命名空间,称为内置命名空间,这个命名空间在Python解释器启动的时候自动创建,其中包含了内置对象的名字,包括内置类和内置函数,如int, list和print, len等。内置命名空间无法修改。

局部命名空间(local namespace)

除了全局命名空间和内置命名空间外,当在程序调用函数的时候,会创建属于这个函数的局部命名空间。局部命名空间中包含该函数的函数体中定义的名字,当函数返回,属于该函数的局部命名空间被销毁。

下面的例子将说明内置、全局和局部命名空间的工作方式。

# file: test2.py
a = 1
def foo():
    a = 2
    print(a)
foo()
print(a)
------------------
result:
2
1

程序的执行分为如下步骤:

当执行test2.py时,首先python解释器在启动时创建内置命名空间,接着为模块文件test2.py创建一个空的全局命名空间。然后开始执行test2.py中的语句,整型对象1被创建,a被加入全局命名空间中;其次创建函数对象和名字foo,foo被加入全局命名空间。此时只存在全局和内置两个命名空间,因为Python只有调用函数的时候,才会执行函数体中的代码。当执行foo()时,Python解释器会对名字foo进行求值,根据作用域规则,解释器会先在全局命名空间中查找foo,如果找不到才会去内置命名空间中查找。结果会在全局命名空间中找到foo并返回其引用的函数对象,随后调用这个函数对象。这时Python解释器会创建一个空的局部命名空间,然后跳转到函数内部执行函数体中的语句。a = 2 创建对象2并命名为a, 名字a被加入到该函数的局部命名空间中;执行print(a)时,首先对a求值,这时函数的局部命名空间和全局命名空间中都有名字a,python会优先在局部命名空间中进行查找,其次是全局命名空间,最后是内置命名空间,因此对a求值返回2,然后再对名字print求值,还是按照局部命名空间,全局命名空间,内置命名空间的顺序,最终会在内置命名空间找到该名字,返回print对应的内置函数对象,以整型对象2为参数,将器输出到标准输出流中。函数foo执行完所有的内部语句返回,函数调用的流程结束,属于该函数的局部命名空间被销毁,并跳转回主程序。最后执行print(a)时,只存在全局和内置两个命名空间,结果分别在全局和内置命名空间中查找到a和print的值。最终打印输出1。

在一个Python程序运行的某个时刻,可以同时存在多个命名空间,各个命名空间之间相互独立。这就允许不同的命名空间中拥有相同的名字,当时使用这个名字时,Python解释器以特定的顺序到各个命名空间中查找并返回对应的对象。

核心问题只有两个:

在程序中创建的名字属于哪个命名空间?对一个名字求值时,怎样在命名空间中去寻找?

一个名字到底属于哪个命名空间,是由名字定义的位置决定的。

对一个名字求值时,会按照一定顺序搜索存在的命名空间,在test1.py和test2.py的例子中PHP命名空间,已经看到了这一点。现在更详细的说明这个问题。

首先,可以设想一下只有一个扁平的命名空间,而不是同时存在多个独立的命名空间的情况。当所有的名字都在一个命名空间中,看起来事情会变得更简单,因为定义的所有名字都添加到这个命名空间中,而且对名字进行求值时也只需要在这一个命名空间查找名字即可。但代价是为了维持对象,就必须为不同的对象取不同的名字,否则将发生命名冲突。这会让编程变得如履薄冰,难以阅读,简直是一种灾难。

就像一本数学著作会反复使用a, b, c, x, y, z等符号,而不是只允许它们使用一次来特指某个特定的量。重复使用这些符号根本不会产生问题,因为读者完全可以根据上下文理解它们代表什么。比如,在介绍勾股定理时,作者会声明a, b, c分别为直角三角形的两个直角边和一个斜边的长度,然后给出 a^2 + b^2 = c^2 的结论。在当前的语境下,我们会很清楚 c 指的就是三角形的斜边长度,而不是其他地方介绍的圆的周长。

这个思想很简单,Python也正是这么做的。查询命名空间的顺序,遵循了当前语境优先的准则。

这里说的语境,就是计算机语言中的上下文(context)或环境(environment)。因为Python是使用词法作用域(lexical scope),所以上下文就是词法上下文(lexical context)。词法上下文是由源代码决定的,与运行时上下文(run time context)没有关系。

定义在模块文件顶层的函数,当工作在其函数体中时,当前上下文就是该函数的局部命名空间,外部的上下文依次为:全局命名空间和内置命名空间。

名字解析所遵循的规则就是按照上下文依次从内到外查找。

PHP命名空间_空间相册的命名_php命名空间的使用

命名空间的查找顺序

外层命名空间(enclosing namespace)

在一个函数内部定义的另一个函数,称为嵌套函数(nested function),为了阐述方便,我们可把嵌套函数称为内函数,定义嵌套函数的函数称为外函数。当在程序中调用外函数时,解释器会创建一个属于外函数的命名空间,当在外函数内部定义一个内函数,并随后在外函数的函数体内调用这个内函数时,解释器又为内函数创建一个命名空间。在内函数的函数体工作时,内函数的命名空间就是局部命名空间,外函数的命名空间称为了一种环境或上下文,被称为外层命名空间,在外层命名空间之外是全局命名空间和内置命名空间。

嵌套函数内部,还可以定义另一个嵌套函数。如下图所示:

php命名空间的使用_空间相册的命名_PHP命名空间

多层函数嵌套

函数连续的嵌套3层,比如在模块顶层定义并调用函数f1,f1内创建并调用f2,f2内创建并调用f3,f3内创建并调用f4,会创建4个分别属于f1、f2、f3、f4的局部命名空间:L1、L2、L3、L4。

下面举例说明:

# file: test3.py
a = 1
def outer():
    a = 2
    def inner():
        print(a)
    inner()
outer()
print(a)
--------------------
result:
2
1

执行test3.py文件,程序的执行分为如下步骤:

1、在执行outer()之前,命名空间状态:

2、执行outer(),进入outer的函数体内,在执行a = 2 之前,命名空间状态:

3、在outer内,执行inner()之前,命名空间状态:

4、执行inner(),进入inner的函数体内,在执行print(a)之前,命名空间状态:

5、执行inner内的print(a),按照L(inner)、L(outer)、G和B从内外到外的顺序查找a,在L(outer)中找到a并返回其引用的对象2。print(a)归约(reduce)为print(2),按照同样的顺序查找print,在B中找到,并返回其引用的函数对象,以2为参数调用该函数对象,打印2。

6、inner()执行结束,返回到outer函数内,命名空间状态:

7、outer()执行结束,并返回主程序内,命名空间状态:

8、接着执行print(a),按照相同的从内到外的规则,不难知道,最终的结果是打印1,程序结束。

作用域(scope)

作用域的概念比较复杂。且容易和命名空间混淆。

名字绑定的作用域指的是名字绑定的可见范围。在词法作用域中“可见范围”指的是源代码中代码范围。

在Python中,名字绑定作用域完全由命名空间和命名空间查找顺序决定。

(编辑:航空爱好网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!