问题描述
我通过编写实现了一个简约的枚举
class SymbolClass(type):
def __repr__(cls): return cls.__qualname__
class Symbol(Metaclass=SymbolClass): pass
class Animal:
class Dog(Symbol): pass
class Cat(Symbol): pass
class Cow(Symbol): pass
效果很好
>>> {Animal.Dog: 'Wan Wan',Animal.Cat: 'Nyaa',Animal.Cow: 'Moooh'}
{Animal.Dog: 'Wan Wan',Animal.Cow: 'Moooh'}
class Symbol:
@classmethod
def __repr__(cls): return cls.__qualname__
class Animal:
class Dog(Symbol): pass
class Cat(Symbol): pass
class Cow(Symbol): pass
但效果不佳:
>>> {Animal.Dog: 'Wan Wan',Animal.Cow: 'Moooh'}
{<class '__main__.Animal.Dog'>: 'Wan Wan',<class '__main__.Animal.Cat'>: 'Nyaa',<class '__main__.Animal.Cow'>: 'Moooh'}
然后我写了一小段测试代码
class SymbolClass(type):
def __repr__(cls): return cls.__qualname__
def __str__(cls): return cls.__name__
class SymbolA(Metaclass=SymbolClass): pass
class SymbolB:
@classmethod
def __repr__(cls): return cls.__qualname__
class SymbolC:
def __repr__(cls): return cls.__qualname__
class Animal:
class Dog(SymbolA): pass
class Cat(SymbolB): pass
class Cow(SymbolC): pass
import sys
def printcompare(n1,l1n,l1v,n2,l2n):
v1 = eval(n1)
v2 = eval(n2)
file = sys.stdout if v2==v1 else sys.stderr
print(f'{n1:>{l1n}} = {v1+",":<{l1v+2}} {n2:>{l2n}} = {v2}',file=file,flush=True)
for name in "SymbolClass","SymbolA","SymbolB","SymbolC","Animal","Animal.Dog","Animal.Cat","Animal.Cow":
printcompare(f"{name}.__qualname__",24,11,f"repr({name})",16)
这为我提供了以下结果:
我不知道是什么造成了这种差异。看起来@classmethod 就像一个普通方法一样工作......
解决方法
您可以追溯这两种情况下发生的情况以了解差异。
dict 打印其键和值的 __repr__
。由于 __repr__
是一个魔法方法,python 不会在实例上查找它。理解这一点至关重要。调用 repr(obj)
时,绑定如下:
type(obj).__repr__(obj) # No binding happening,just calling function
不是
obj.__repr__.__get__(obj)() # Binding of non-data function descriptor
你得到了前者,但期待后者。
元类
type(Animal.Dog).__repr__(Animal.Dog)
完全等同于 SymbolClass.__repr__(Animal.Dog)
,它只返回准确的字符串 Animal.Dog.__qualname__
。这里几乎没有歧义。但是,会出现一些混淆,因为如果您忽略 __repr__
已优化为绑定到类的事实,您将得到相同的结果。也就是说,Animal.Dog.__repr__()
将跟随 MRO 到 Animal.Dog
的类型,并使用 __repr__
的隐式 self
调用 Animal.Dog
。你不会看到任何区别。
类方法
然而,当您使用 classmethod
时,您正试图绕过 Python 对魔法方法的优化绑定。这是因为 classmethod
首先依赖于作为描述符绑定,而是作为简单函数调用。
这是第一个键的实际情况:
type(Animal.Dog).__repr__(Animal.Dog)
这归结为type.__repr__(Animal.Dog)
。请注意,如果没有元类,您现在调用的是默认的 __repr__
,而不是自定义的。此时输出应该非常清楚。
如果魔术方法机制按您的预期工作,您将改为执行 Animal.Dog.__repr__()
,这会按预期工作,因为它现在可以通过 __repr__
正确地将 classmethod
绑定到类,并将 Animal.Dog
作为 cls
传递。您可以尝试以这种方式调用它,看看会发生什么。事实上,即使这个调用约定也能工作,因为 classmethod
装饰器不会盲目地将 type(self)
传递给包装方法,而是首先检查它是否是父类对象。