以声明方式设置类 __name__

问题描述 投票:0回答:1

为什么不能以声明方式覆盖类名,例如使用不是有效标识符的类名?

>>> class Potato:
...     __name__ = 'not Potato'
...     
>>> Potato.__name__  # doesn't stick
'Potato'
>>> Potato().__name__  # .. but it's in the dict
'not Potato'

我想这可能只是类定义块完成后被覆盖的情况。但似乎这不是真的,因为名称是可写的,但显然 not 在类字典中设置:

>>> Potato.__name__ = 'no really, not Potato'
>>> Potato.__name__  # works
'no really, not Potato'
>>> Potato().__name__  # but instances resolve it somewhere else
'not Potato'
>>> Potato.__dict__
mappingproxy({'__module__': '__main__',
              '__name__': 'not Potato',  # <--- setattr didn't change that
              '__dict__': <attribute '__dict__' of 'no really, not Potato' objects>,
              '__weakref__': <attribute '__weakref__' of 'no really, not Potato' objects>,
              '__doc__': None})
>>> # the super proxy doesn't find it (unless it's intentionally hiding it..?)
>>> super(Potato).__name__
AttributeError: 'super' object has no attribute '__name__'

问题:

  1. Potato.__name__
    在哪里解决?
  2. 如何处理
    Potato.__name__ = other
    (在类定义块内部和外部)?
python metaclass python-datamodel python-descriptors python-object
1个回答
11
投票

Potato.__name__
在哪里解决?

大多数记录的 dunder 方法和属性实际上存在于对象的本机代码端。对于 CPython,它们被设置为对象模型中定义的 C 结构中槽中的指针。 (此处定义 - https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Include/object.h#L346,但当人们实际在 C 中创建一个新类时,字段更容易可视化,如下所示: https://github.com/python/cpython/blob/04e82934659487ecae76bf4a2db7f92c8dbe0d25/Objects/typeobject.c#L7778,其中定义了“super”类型)

因此,

__name__
type.__new__
中的代码设置,它是第一个参数。

如何处理

Potato.__name__
= other(在类定义块内部和外部)?

类的

__dict__
参数不是一个普通的字典——它是一个特殊的映射代理对象,这样做的原因正是为了让类本身的所有属性设置不经过
__dict__
,而是走通过 type 中的
__setattr__
方法。在那里,对这些 slotted dunder 方法的赋值实际上填充在 C 对象的 C 结构中,然后反映在
class.__dict__
属性上。

因此,在类块之外

cls.__name__
以这种方式设置 - 因为它是在创建类之后发生的。

在类块内部,所有属性和方法都收集到一个普通的字典中(尽管可以自定义)。该字典被传递给 type.__new__

 和其他元类方法 - 但如上所述,该方法从显式传递的 
__name__
 参数(即在调用 
时传递的“name”参数)填充
name
槽。 type.__new__
)- 即使它只是更新类 
__dict__
 代理,并将字典中的所有名称用作命名空间。

这就是为什么

cls.__dict__["__name__"]

 可以从 
cls.__name__
 插槽中不同的内容开始,但后续分配会使两者同步。

一个有趣的轶事是,三天前我遇到了一些代码,试图在类主体中显式重用

__dict__

 名称,这也具有类似的令人费解的副作用。
我什至想知道是否应该有一个关于此的错误报告,并询问了Python开发人员 - 正如我所想到的,权威的答案是:

...所有

__dunder__

 名称都是为实现而保留的,它们应该
只能根据文档使用。所以说确实不违法
但也不能保证任何事情都有效。
(G. 范罗森)

它同样适用于尝试在类主体中定义

__name__

https://mail.python.org/pipermail/python-dev/2018-April/152689.html


如果确实想将

__name__

 重写为类主体中的一个属性,那么元类就很简单,就像元类一样:

class M(type): def __new__(metacls, name, bases, namespace, **kw): name = namespace.get("__name__", name) return super().__new__(metacls, name, bases, namespace, **kw)
    
© www.soinside.com 2019 - 2024. All rights reserved.