如何使用自定义类方法覆盖元类中定义的特殊方法?
How can I override a special method defined in a metaclass with a custom classmethod?
例如,请考虑以下内容:
class FooMeta(type):
def __len__(cls):
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
len(GoodBar) -> 9000
len(GoodBar()) -> 9001
GoodBar.__len__() -> TypeError (missing 1 required positional argument)
GoodBar().__len__() -> 9001
len(BadBar) -> 9000 (!!!)
len(BadBar()) -> 9002
BadBar.__len__() -> 9002
BadBar().__len__() -> 9002
问题在于 len(BadBar)
返回 9000 而不是预期行为的 9002。
此行为(在某种程度上)记录在 Python Data Model - Special Method Lookup 中,但它没有提及任何关于类方法的内容,而且我不太了解与 @classmethod
装饰器的交互。
除了明显的元类解决方案(即 replace/extend FooMeta
)之外,是否有一种方法可以覆盖或扩展元类功能,以便 len(BadBar) -> 9002
?
编辑:
澄清一下,在我的特定用例中,我无法编辑元类,并且我不想将其子类化 and/or 创建我自己的元类,除非它是唯一的 这样做的可能方式。
正如您已经提到的,len()
将根据对象的类型而不是对象本身查找 __len__
。所以 len(BadBar)
被称为 type(BadBar).__len__(BadBar)
,FooMeta
的 __len__
方法正在获取实例,这里是 BadBar
作为第一个参数。
class FooMeta(type):
def __len__(cls):
print(cls is BadBar)
return 9000
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
print(len(BadBar)) # True - 9000
这就是它的工作原理,如果您对 metaclass 的 __len__
进行任何操作,其他具有该 metaclass 的 classes 将受到影响。不能一概而论。
您可以为 BadBar
设置另一个特定的元class,如果有的话,在元class 的 __len__
中调用 cls.__len__
,否则 return 默认值。
class BadBar_meta(type):
def __len__(cls):
if hasattr(cls, "__len__"):
return cls.__len__()
return 9000
class BadBar(metaclass=BadBar_meta):
@classmethod
def __len__(cls):
return 9002
print(len(BadBar)) # 9002
或者如果你只想重写 classes 的 class 方法,你可以检查 cls
class 是否已经定义 __len__
作为 class 方法,如果是这样的话,请改为调用它。
class FooMeta(type):
def __len__(cls):
m = cls.__len__
if hasattr(m, "__self__"):
return cls.__len__()
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
print(len(GoodBar)) # 9000
print(len(BadBar)) # 9002
Here 是寻找 class 方法的更可靠方法。
编辑后:
如果您无法访问元数据class,那么您可以对元数据进行装饰和猴子修补class:
class FooMeta(type):
def __len__(cls):
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
def decorator(mcls):
org_len = mcls.__len__
def custom_len(cls):
# check too see if it is a classmethod
if hasattr(cls.__len__, "__self__"):
return cls.__len__()
return org_len(cls)
FooMeta.__len__ = custom_len
return mcls
FooMeta = decorator(FooMeta)
print(len(GoodBar)) # 9000
print(len(BadBar)) # 9002
在 class 中定义的 __len__
在对 class 本身使用 len(...)
时将始终被忽略:在执行其运算符和“哈希”等方法时, "iter", "len" 可以粗略的说是具有"运算符状态", Python总是从目标的class中获取对应的方法,通过直接访问[=的内存结构81=]。这些 dunder 方法在 class 的内存布局中具有“物理”插槽:如果该方法存在于实例的 class 中(在这种情况下,“实例”是 classes“GoodBar”和“BadBar”,“FooMeta”的实例),或其超classes之一,它被调用 - 否则运算符失败。
因此,这就是适用于 len(GoodBar())
的推理:它将调用 GoodBar()
的 class 中定义的 __len__
和 len(GoodBar)
len(BadBar)
将调用在其 class、FooMeta
中定义的 __len__
I don't really understand the interaction with the @classmethod
decorator.
“classmethod”装饰器从装饰函数中创建了一个特殊的描述符,因此当它被检索时,通过 class 中的“getattr”它也被绑定,Python 创建一个带有“cls”参数的“部分”对象。就像从实例中检索一个普通方法创建一个带有“self”的对象 pre-bound:
这两件事都通过“描述符”协议进行 - 这意味着,普通方法和 class 方法都可以通过调用其 __get__
方法来检索。这个方法有 3 个参数:“self”,描述符本身,“instance”,它绑定到的实例,以及“owner”:它绑定到的 class。问题是对于普通方法(函数),当 __get__
的第二个(实例)参数是 None
时,函数本身被返回。 @classmethod
用具有不同 __get__
的对象包装函数:returns 相当于 partial(method, cls)
,而不管 __get__
的第二个参数。
换句话说,这个简单的纯 Python 代码复制了 classmethod
装饰器的工作:
class myclassmethod:
def __init__(self, meth):
self.meth = meth
def __get__(self, instance, owner):
return lambda *args, **kwargs: self.meth(owner, *args, **kwargs)
这就是为什么您在使用 klass.__get__()
和 klass().__get__()
显式调用 class 方法时会看到相同的行为:实例被忽略。
TL;DR: len(klass)
将始终通过 metaclass 插槽,并且 klass.__len__()
将检索 __len__
通过 getattr 机制,然后在调用之前正确绑定 class 方法。
Aside from the obvious metaclass solution (ie, replace/extend FooMeta)
is there a way to override or extend the metaclass function so that
len(BadBar) -> 9002?
(...)
To clarify, in my specific use case I can't edit the metaclass, and I
don't want to subclass it and/or make my own metaclass, unless it is
the only possible way of doing this.
没有别的办法。 len(BadBar)
将始终通过 metaclass __len__
.
不过,扩展元class 可能并不那么痛苦。
可以通过简单地调用 type
传递新的 __len__
方法来完成:
In [13]: class BadBar(metaclass=type("", (FooMeta,), {"__len__": lambda cls:9002})):
...: pass
In [14]: len(BadBar)
Out[14]: 9002
只有当 BadBar 稍后将与另一个 class 具有 不同 自定义元 class 层次结构的多重继承组合时,您才需要担心。即使有其他 classes 将 FooMeta
作为 metaclass,上面的代码片段也会起作用:动态创建的 metaclass 将是 metaclass对于新子class,作为“最衍生的子class”。
但是,如果存在子classes 的层次结构并且它们具有不同的 metaclasses,即使通过此方法创建,您也必须将两个 metaclass 结合起来]在创建新的“普通”子class.
之前在公共subclass_of_the_metaclasses中
如果是这样,请注意您可以有一个单一的可参数化元class,扩展您原来的元(虽然不能回避)
class SubMeta(FooMeta):
def __new__(mcls, name, bases, ns, *,class_len):
cls = super().__new__(mcls, name, bases, ns)
cls._class_len = class_len
return cls
def __len__(cls):
return cls._class_len if hasattr(cls, "_class_len") else super().__len__()
并且:
In [19]: class Foo2(metaclass=SubMeta, class_len=9002): pass
In [20]: len(Foo2)
Out[20]: 9002
例如,请考虑以下内容:
class FooMeta(type):
def __len__(cls):
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
len(GoodBar) -> 9000
len(GoodBar()) -> 9001
GoodBar.__len__() -> TypeError (missing 1 required positional argument)
GoodBar().__len__() -> 9001
len(BadBar) -> 9000 (!!!)
len(BadBar()) -> 9002
BadBar.__len__() -> 9002
BadBar().__len__() -> 9002
问题在于 len(BadBar)
返回 9000 而不是预期行为的 9002。
此行为(在某种程度上)记录在 Python Data Model - Special Method Lookup 中,但它没有提及任何关于类方法的内容,而且我不太了解与 @classmethod
装饰器的交互。
除了明显的元类解决方案(即 replace/extend FooMeta
)之外,是否有一种方法可以覆盖或扩展元类功能,以便 len(BadBar) -> 9002
?
编辑:
澄清一下,在我的特定用例中,我无法编辑元类,并且我不想将其子类化 and/or 创建我自己的元类,除非它是唯一的 这样做的可能方式。
正如您已经提到的,len()
将根据对象的类型而不是对象本身查找 __len__
。所以 len(BadBar)
被称为 type(BadBar).__len__(BadBar)
,FooMeta
的 __len__
方法正在获取实例,这里是 BadBar
作为第一个参数。
class FooMeta(type):
def __len__(cls):
print(cls is BadBar)
return 9000
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
print(len(BadBar)) # True - 9000
这就是它的工作原理,如果您对 metaclass 的 __len__
进行任何操作,其他具有该 metaclass 的 classes 将受到影响。不能一概而论。
您可以为 BadBar
设置另一个特定的元class,如果有的话,在元class 的 __len__
中调用 cls.__len__
,否则 return 默认值。
class BadBar_meta(type):
def __len__(cls):
if hasattr(cls, "__len__"):
return cls.__len__()
return 9000
class BadBar(metaclass=BadBar_meta):
@classmethod
def __len__(cls):
return 9002
print(len(BadBar)) # 9002
或者如果你只想重写 classes 的 class 方法,你可以检查 cls
class 是否已经定义 __len__
作为 class 方法,如果是这样的话,请改为调用它。
class FooMeta(type):
def __len__(cls):
m = cls.__len__
if hasattr(m, "__self__"):
return cls.__len__()
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
print(len(GoodBar)) # 9000
print(len(BadBar)) # 9002
Here 是寻找 class 方法的更可靠方法。
编辑后:
如果您无法访问元数据class,那么您可以对元数据进行装饰和猴子修补class:
class FooMeta(type):
def __len__(cls):
return 9000
class GoodBar(metaclass=FooMeta):
def __len__(self):
return 9001
class BadBar(metaclass=FooMeta):
@classmethod
def __len__(cls):
return 9002
def decorator(mcls):
org_len = mcls.__len__
def custom_len(cls):
# check too see if it is a classmethod
if hasattr(cls.__len__, "__self__"):
return cls.__len__()
return org_len(cls)
FooMeta.__len__ = custom_len
return mcls
FooMeta = decorator(FooMeta)
print(len(GoodBar)) # 9000
print(len(BadBar)) # 9002
在 class 中定义的 __len__
在对 class 本身使用 len(...)
时将始终被忽略:在执行其运算符和“哈希”等方法时, "iter", "len" 可以粗略的说是具有"运算符状态", Python总是从目标的class中获取对应的方法,通过直接访问[=的内存结构81=]。这些 dunder 方法在 class 的内存布局中具有“物理”插槽:如果该方法存在于实例的 class 中(在这种情况下,“实例”是 classes“GoodBar”和“BadBar”,“FooMeta”的实例),或其超classes之一,它被调用 - 否则运算符失败。
因此,这就是适用于 len(GoodBar())
的推理:它将调用 GoodBar()
的 class 中定义的 __len__
和 len(GoodBar)
len(BadBar)
将调用在其 class、FooMeta
__len__
I don't really understand the interaction with the @classmethod decorator.
“classmethod”装饰器从装饰函数中创建了一个特殊的描述符,因此当它被检索时,通过 class 中的“getattr”它也被绑定,Python 创建一个带有“cls”参数的“部分”对象。就像从实例中检索一个普通方法创建一个带有“self”的对象 pre-bound:
这两件事都通过“描述符”协议进行 - 这意味着,普通方法和 class 方法都可以通过调用其 __get__
方法来检索。这个方法有 3 个参数:“self”,描述符本身,“instance”,它绑定到的实例,以及“owner”:它绑定到的 class。问题是对于普通方法(函数),当 __get__
的第二个(实例)参数是 None
时,函数本身被返回。 @classmethod
用具有不同 __get__
的对象包装函数:returns 相当于 partial(method, cls)
,而不管 __get__
的第二个参数。
换句话说,这个简单的纯 Python 代码复制了 classmethod
装饰器的工作:
class myclassmethod:
def __init__(self, meth):
self.meth = meth
def __get__(self, instance, owner):
return lambda *args, **kwargs: self.meth(owner, *args, **kwargs)
这就是为什么您在使用 klass.__get__()
和 klass().__get__()
显式调用 class 方法时会看到相同的行为:实例被忽略。
TL;DR: len(klass)
将始终通过 metaclass 插槽,并且 klass.__len__()
将检索 __len__
通过 getattr 机制,然后在调用之前正确绑定 class 方法。
Aside from the obvious metaclass solution (ie, replace/extend FooMeta) is there a way to override or extend the metaclass function so that len(BadBar) -> 9002?
(...) To clarify, in my specific use case I can't edit the metaclass, and I don't want to subclass it and/or make my own metaclass, unless it is the only possible way of doing this.
没有别的办法。 len(BadBar)
将始终通过 metaclass __len__
.
不过,扩展元class 可能并不那么痛苦。
可以通过简单地调用 type
传递新的 __len__
方法来完成:
In [13]: class BadBar(metaclass=type("", (FooMeta,), {"__len__": lambda cls:9002})):
...: pass
In [14]: len(BadBar)
Out[14]: 9002
只有当 BadBar 稍后将与另一个 class 具有 不同 自定义元 class 层次结构的多重继承组合时,您才需要担心。即使有其他 classes 将 FooMeta
作为 metaclass,上面的代码片段也会起作用:动态创建的 metaclass 将是 metaclass对于新子class,作为“最衍生的子class”。
但是,如果存在子classes 的层次结构并且它们具有不同的 metaclasses,即使通过此方法创建,您也必须将两个 metaclass 结合起来]在创建新的“普通”子class.
之前在公共subclass_of_the_metaclasses中如果是这样,请注意您可以有一个单一的可参数化元class,扩展您原来的元(虽然不能回避)
class SubMeta(FooMeta):
def __new__(mcls, name, bases, ns, *,class_len):
cls = super().__new__(mcls, name, bases, ns)
cls._class_len = class_len
return cls
def __len__(cls):
return cls._class_len if hasattr(cls, "_class_len") else super().__len__()
并且:
In [19]: class Foo2(metaclass=SubMeta, class_len=9002): pass
In [20]: len(Foo2)
Out[20]: 9002