Python命名空间中的名称分配和重用

3

我目前正在研究Python中的mappingproxies和命名空间内部。在测试时,我遇到了一些意料之外的行为。

我定义了一个类,实例化它,然后在同样的名称下定义另一个类并实例化它。

class A:
    color = 'white'
    def __init__(self, val):
        self.val = val

a1 = A(1)

class A:
    color = 'black'
    def __init__(self, val):
        self.val = val

a2 = A(2)

现在,a1和a2是两个不同类的实例对象,它们都仍然存在。在命名空间中,第二个被修改过的类版本现在被赋给了名称A。而第一个最初的类对象只能通过其实例的__class__属性来访问。尽管如此,您仍然可以完全与旧的类对象进行交互。

print(a1.__class__ is a2.__class__)     # False
print(a1.__class__ is A)                # False
print(a2.__class__ is A)                # True
a3 = a1.__class__(3)
print(a3.__class__ is a1.__class__)     # True
print(a3.color)                         # white
a3.__class__.color = 'red'
print(a1.color)                         # red

我猜想Python的对象引用计数器是导致旧类对象仍然存在的原因,因为当新的类对象被分配给名称A时,计数器并不为零,因为a1仍然保持一个引用。至于我的问题:这是预期的行为吗?如果是这样,背后的原因是什么?对我来说,这看起来有点太隐晦了。老实说,我希望它失败并抛出一些异常。

编辑

为了解释为什么这让我感到震惊,并回答下面评论中的Daniel Roseman的问题:

a1和a2都认为自己是__main__.A的实例,而实际上它们中的一个是曾经的__main__.A的实例。这使我拥有了一个活动且可用的对象,在我的命名空间中没有明确定义的句柄。我认为这是由于类对象具有类对象二元性质所特有的问题。

我正在开发一些基于奇怪输入动态构建类并在其实例上执行命令序列的工具。这涉及到很多动态导入。不知道这种行为,我可能会结束调试类及其构建过程,而实际上我应该查看一些线程问题。


我的看法是:我认为必须处理一个“死”类的实例会引起比保持该类“活着”更多的问题。 - polku
1
我不明白为什么你认为这个方法不能工作,或者说这是意料之外的行为。这似乎完全符合类只是对象、"A"只是一个名称以及一个对象可以有多个名称或没有名称的原则。 - Daniel Roseman
1个回答

2

这是预期行为吗?

是的。

背后的原因是什么?

正如丹尼尔·罗斯曼在上面的评论中所指出的,这是 Python 中一切皆为对象的一般规则的特定情况。

考虑以下代码片段:

some_string_a = 'white'
some_string_b = 'black'

a = some_string_a
print(a)

    >>> white

a = some_string_b
print(a)

    >>> black

print(some_string_a)

    >>> white

我相信你不会感到惊讶,即使一个变量名曾经引用过some_string_a,现在已经被重新分配并引用了其他东西,你仍然可以与它进行交互。

但是,由于你问题中提到的两个类也都是对象,所以这是一个类比的情况。既然你已经重新分配了变量名(A),使其现在指向了另一个对象,为什么不能与一个对象(你定义的第一个类)进行交互呢?

正如你在编辑中提到的那样,这可能会导致一些命名空间的奇怪问题......但这是Python为了保持一致性而付出的代价,因为Python将所有东西都视为对象。


在你的例子中,你的两个字符串对象都没有被显式地定义名称分配。但是类对象却发生了这种情况。我希望我的运行时能够给我正确的关于其状态的信息。在我的例子中,声称a1是__main__.A的实例显然是错误的信息。然而,我接受这个答案,因为它解释了Python所做的设计决策导致的行为。谢谢! - shmee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接