例如,请考虑以下内容:
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,这是预期的行为。
此行为(某种程度上)在,但它没有提及ClassMethods,我真的不理解与 @ClassMethod
Decorator的互动。
除了明显的Metaclass解决方案(即,替换/扩展 foometa
),还有一种方法可以覆盖或扩展MetaClass函数,以便 len(badbar) - > 9002
?
编辑:
要澄清,在我的特定用例中,我不能编辑元素,我不想对其进行亚类和/或制作自己的元素这样做。
As an example, consider the following:
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
The issue being with len(BadBar)
returning 9000 instead of 9002 which is the intended behaviour.
This behaviour is (somewhat) documented in Python Data Model - Special Method Lookup, but it doesn't mention anything about classmethods, and I don't really understand the interaction with the @classmethod
decorator.
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
?
Edit:
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.
发布评论
评论(2)
当使用
len(...)
对类本身使用__ len __
,始终将忽略类中的 当执行其运算符时,以及诸如“哈希”,“”,“可以粗略地说iTer”,“ len”具有“操作员状态”,Python总是通过直接符合类的内存结构来从目标类中检索相应的方法。这些dunder方法在类的内存布局中具有“物理”插槽:如果该方法存在于您实例的类中(在这种情况下,“实例”是“ boodbar”和“ badbar”类,则是“ badbar”,则是“ badbar”的实例。 foometa”)或它的超类之一,被称为 - 否则操作员会失败。因此,这是适用于
len(goodbar())
的推理:它将调用__ len __
在goodbar()
的类中定义的 ,和len(goodbar)
和len(badbar)
将调用__ len __ __
在其类中定义的,foometa
“ classMethod”装饰器从装饰函数中创建了一个特殊的描述符,因此当通过“ getAttr”从类中检索它时,python也可以使用“ cls”参数创建一个“部分”对象。就像从一个实例中检索普通方法一样,用“自我”预绑来创建一个对象一样:
这两种内容都是通过“描述符”协议携带的 - 这意味着,通过调用其
__ get__ get __来检索一个普通方法和classMethod
方法。此方法采用3个参数:“自我”,描述符本身,“实例”,实例与“ body”和“所有者”:类别的类别。事实是,对于普通方法(函数),第二个(实例)参数到__获取__ get __
isnone
none 时,函数本身将返回。@ClassMethod
用不同的对象包装一个函数__获取__
:一个返回等同于partial(方法,cls)
的函数第二个参数到__获取__
。换句话说,这个简单的纯Python代码复制
ClassMethod
Decorator的工作:这就是为什么您在使用
klass.__ get __()
明确调用ClassMethod时会看到相同的行为。Klass().__获取__()
:该实例被忽略。tl; dr :
len(klass)
将始终通过Metaclass插槽,klass .__ len __()
将检索/code>通过getAttr机制,然后在调用之前正确绑定分类。没有其他方法。
len(badbar)
将始终通过Metaclass__ Len __
。不过,扩展元口可能并不那么痛苦。
可以通过简单调用
type
传递新的__ len __
方法:仅当稍后将与其他类别的层次结构合并为 不同的 自定义元素您必须担心。即使还有其他类
foometa
作为metaclass的类,上面的摘要也将起作用:动态创建的元口将是新子类的元类,作为“最派生的子类”。但是,如果有一个子类的层次结构,并且它们具有不同的元类别,即使通过这种方法创建,您也必须将两个元素组合在公共 subclass_of_the_metaclasses 之前,然后再创建新的“普通”子类。
如果是这种情况,请注意,您可以拥有一个可出现的元素,扩展您的原始元素(尽管不能躲闪),
并且:
The
__len__
defined in the class will always be ignored when usinglen(...)
for the class itself: when executing its operators, and methods like "hash", "iter", "len" can be roughly said to have "operator status", Python always retrieve the corresponding method from the class of the target, by directly acessing the memory structure of the class. These dunder methods have "physical" slot in the memory layout for the class: if the method exists in the class of your instance (and in this case, the "instances" are the classes "GoodBar" and "BadBar", instances of "FooMeta"), or one of its superclasses, it is called - otherwise the operator fails.So, this is the reasoning that applies on
len(GoodBar())
: it will call the__len__
defined inGoodBar()
's class, andlen(GoodBar)
andlen(BadBar)
will call the__len__
defined in their class,FooMeta
The "classmethod" decorator creates a special descriptor out of the decorated function, so that when it is retrieved, via "getattr" from the class it is bound too, Python creates a "partial" object with the "cls" argument already in place. Just as retrieving an ordinary method from an instance creates an object with "self" pre-bound:
Both things are carried through the "descriptor" protocol - which means, both an ordinary method and a classmethod are retrieved by calling its
__get__
method. This method takes 3 parameters: "self", the descriptor itself, "instance", the instance its bound to, and "owner": the class it is ound to. The thing is that for ordinary methods (functions), when the second (instance) parameter to__get__
isNone
, the function itself is returned.@classmethod
wraps a function with an object with a different__get__
: one that returns the equivalent topartial(method, cls)
, regardless of the second parameter to__get__
.In other words, this simple pure Python code replicates the working of the
classmethod
decorator:That is why you see the same behavior when calling a classmethod explicitly with
klass.__get__()
andklass().__get__()
: the instance is ignored.TL;DR:
len(klass)
will always go through the metaclass slot, andklass.__len__()
will retrieve__len__
via the getattr mechanism, and then bind the classmethod properly before calling it.There is no other way.
len(BadBar)
will always go through the metaclass__len__
.Extending the metaclass might not be all that painful, though.
It can be done with a simple call to
type
passing the new__len__
method:Only if BadBar will later be combined in multiple inheritance with another class hierarchy with a different custom metaclass you will have to worry. Even if there are other classes that have
FooMeta
as metaclass, the snippet above will work: the dynamically created metaclass will be the metaclass for the new subclass, as the "most derived subclass".If however, there is a hierarchy of subclasses and they have differing metaclasses, even if created by this method, you will have to combine both metaclasses in a common subclass_of_the_metaclasses before creating the new "ordinary" subclass.
If that is the case, note that you can have one single paramtrizable metaclass, extending your original one (can't dodge that, though)
And:
如您已经提到的,
len()
将在对象的类型不是对象本身上找到__ Len __
。因此,len(badbar)
被称为type(badbar)。方法是在此处获取
badbar
的实例作为第一个参数。这就是它的工作原理,如果您使用Metaclass的
__ len __
做任何事情,那么与该Metaclass的其他类别将受到影响。您无法概括它。您可以为
badbar
拥有另一个特定的元素价值。或者,如果您只想覆盖类的分类,则可以检查
cls
class是否已经定义__ len __
为classMethod,如果是这样,请使用。这里是找到ClassMethods的更强大方法。
编辑后:
如果您无法访问Metaclass,则可以装饰和猴子补丁元素:
As you already mentioned,
len()
is going to find__len__
on the object's type not the object itself. Solen(BadBar)
is called liketype(BadBar).__len__(BadBar)
,FooMeta
's__len__
method is getting the instance which isBadBar
here as the first parameter.This is how it works, if you do anything with metaclass's
__len__
, other classes with that metaclass will be affected. You can not generalize it.You can have another specific metaclass for
BadBar
and call thecls.__len__
inside the__len__
of the metaclass if it has one, otherwise return the default value.Or if you just want to override only classmethods of the classes, you can check to see if the
cls
class has already defined__len__
as a classmethod, if so call that instead.Here are more robust approaches to find classmethods.
After your edit:
If you don't have access to the metaclass, then you can decorate and monkey patch the metaclass: