我怎样才能默默地让嵌套 class 知道它的外部 class(通过将所有必需的代码放在父级或元 class 中)?

How can I silently make a nested class aware of it's outer class (by putting all the required code in parent or metaclass)?

我制作了一个符号 class,它可以帮助我使用 metaclasses

来制作符号常量
>>> class SymbolClass(type):
    def __repr__(self): return self.__name__

>>> class Symbol(metaclass=SymbolClass): pass

>>> class Spam(Symbol) : pass

>>> class Egg(Symbol) : pass

>>> class Banana(Symbol) : pass

>>> mydict = {Egg:"Where is the bacon", Banana:(2, 3, 4)}

>>> mydict
{Egg: 'Where is the bacon', Banana: (2, 3, 4)}

但是,如果我想通过将此类符号嵌套在外部 class 中来将其用于枚举,我将需要一种方法让符号内部 class 知道它们的外部 class。在当前的实施中,会发生以下情况:

>>> class Animal:
        class Dog(Symbol): pass
        class Cat(Symbol): pass

>>> print(repr(Animal.Dog))
Dog

从打印 str(Animal.Dog) 得到这个回复是可以的,但是当打印 repr(Animal.Dog) 时,我想得到 Animal.Dog.

那么,我怎样才能使嵌套的 Animal.DogAnimal.Cat classes 知道它们的外部 Animal class,而不会使它们复杂化宣言。我想拥有所有需要的代码,隐藏在超级 classes(SymbolAnimal 的超级 class)或它们的元 classes 中.

您需要 Animal 继承自 Enum:

from enum import Enum

class Animal(Enum):
    class Dog(Symbol): pass
    class Cat(Symbol): pass

>>> print(repr(Animal.Dog))
<Animal.Dog: Dog>

>>> print(Animal.Dog)
Animal.Dog

如果您想要不同的 str()repr(),您需要定义它们:

class Animal(Enum):
    #
    def __repr__(self):
        return "%s.%s" % (self.__class__.__name__, self._name_)
    #
    def __str__(self):
        return self._name_
    #
    class Dog(Symbol): pass
    class Cat(Symbol): pass

与name对比的__qualname__包含了封装classes或函数的信息:


In [13]: class Symbol(type):
    ...:     def __repr__(cls):
    ...:         return cls.__qualname__
    ...: 
    ...: 

In [14]: class Dog(metaclass=Symbol): pass

In [15]: Dog
Out[15]: Dog

In [16]: class Animal:
    ...:     class Dog(metaclass=Symbol): pass
    ...: 

In [17]: Animal.Dog
Out[17]: Animal.Dog

当然,如果你真的想使用枚举,Python的enum.Enum可以涵盖大量的用例。有时我只是觉得它有点矫枉过正,并使用一些更简单的 ad-hoc 东西,但是一旦你求助于自定义元 class,它就不再“更简单”了,你可能会遇到一些极端情况该枚举已经解决了。

真正获取有关封闭的信息 class

现在,无论枚举如何,如果有人想使用元class 并了解有关封闭 class body 的信息 - 如本问题的标题所示,事情更棘手。对于 class body 定义内部, class body 中的变量本身是局部变量,全局变量指向模块 globals:没有等效项nonlocal 当封闭范围是一个函数时。

如果设置为 class 属性的 object 需要有关其所有者 class 的信息,Python 实现强大的 descriptor procotol,由语言 built-in property。您所需要的只是在 object 的 class 中定义一个 __get__ 方法,并记下所有者属性:不需要元 classes:

In [56]: class Symbol: # not a metaclass
    ...:     def __get__(self, instance, owner):
    ...:         self.owner = owner
    ...:         return self
    ...:     def __set_name__(self, owner, name):
    ...:         self.name = name
    ...:     def __repr__(self):
    ...:         return f"{self.owner.__name__}.{self.name}"
    ...: 
    ...: 

In [57]: class Animal:
    ...:     Dog = Symbol()
    ...: 

In [58]: Animal.Dog
Out[58]: Animal.Dog

现在,如果在嵌套 class 创建时想要有关封闭范围的信息,这是可行的,但需要在 metaclass' __new__ 并查找封闭范围中的 locals 是否是另一个 class 的 body。一个强烈的暗示是它是否定义了 __module____qualname__ 键。然后你可以访问封闭的 class 命名空间,但是......封闭的 class 本身还不存在:

In [40]: import inspect

In [41]: class M(type):
    ...:     def __new__(mcls, name, bases, ns, **kw):
    ...:         enclosing_frame = inspect.stack()[1].frame
    ...:         print (enclosing_frame.f_locals)
    ...:         return super().__new__(mcls, name, bases, ns, **kw)
    ...: 

In [42]: class A:
    ...:     class B(metaclass=M): pass
    ...: 
{'__module__': '__main__', '__qualname__': 'A'}

因此,如果只需要封闭的 class __name__,那么只需使用 __qualname__ 即可完成。但是,如果只需要名称,__qualname__ 本身就已经设置在嵌套的 class 命名空间中。

如果想要对 __class__ 本身的有效引用,则在创建嵌套 class 本身时是不可能的,因为此时外部 class 不存在: 只会在自己定义的末尾创建body,当然。

幸运的是,如果属性是 class,描述符机制将起作用,并且 __set_name__ 特殊方法在其元 class:

中定义
In [51]: class Symbol(type):
    ...:     def __set_name__(cls, owner, name):
    ...:         assert name == cls.__name__
    ...:         cls.owner = owner
             # it turns out __get__ is not needed for  __set_name__ to work
    ...:     #def __get__(cls, instance, owner):
    ...:         # needed so the attribute is recognized as a descriptor (? - actually: to be checked)
    ...:     #    return cls
    ...:     def __repr__(cls):
    ...:         return f"{cls.owner.__name__}.{cls.__name__}"
    ...: 
    ...: 

In [52]: class Animal:
    ...:     class Dog(metaclass=Symbol): pass
    ...: 

In [53]: Animal.Dog
Out[53]: Animal.Dog

再说一次:问题的最后一部分只是为了那些想要获得对外部 class 的引用而不是获取名称的其他目的的人。现在,检查使用 __get__ 方法的描述符协议是否可以提供此类信息,而根本不需要元 class。