[英]Get defining class of unbound method object in Python 3
Say I want to make a decorator for methods defined in a class.假设我想为类中定义的方法制作一个装饰器。 I want that decorator, when invoked, to be able to set an attribute on the class defining the method (in order to register it in a list of methods that serve a particular purpose).我希望该装饰器在调用时能够在定义该方法的类上设置一个属性(以便将其注册到用于特定目的的方法列表中)。
In Python 2, the im_class
method accomplishes this nicely:在 Python 2 中, im_class
方法很好地完成了这一点:
def decorator(method):
cls = method.im_class
cls.foo = 'bar'
return method
However, in Python 3, no such attribute (or a replacement for it) seems to exist.然而,在 Python 3 中,似乎不存在这样的属性(或它的替代品)。 I suppose the idea was that you could call type(method.__self__)
to get the class, but this does not work for unbound methods, since __self__ == None
in that case.我想这个想法是你可以调用type(method.__self__)
来获取类,但这不适用于未绑定的方法,因为在这种情况下__self__ == None
。
NOTE: This question is actually a bit irrelevant for my case, since I've chosen instead to set an attribute on the method itself and then have the instance scan through all of its methods looking for that attribute at the appropriate time.注意:这个问题实际上与我的情况有点无关,因为我选择在方法本身上设置一个属性,然后让实例在适当的时间扫描其所有方法以查找该属性。 I am also (currently) using Python 2.6.我(目前)也在使用 Python 2.6。 However, I am curious if there is any replacement for the version 2 functionality, and if not, what the rationale was for removing it completely.但是,我很好奇是否有任何替代版本 2 的功能,如果没有,完全删除它的理由是什么。
EDIT : I just found this question .编辑:我刚刚发现这个问题。 This makes it seem like the best solution is just to avoid it like I have.这使得看起来最好的解决方案就是像我一样避免它。 I'm still wondering why it was removed though.我仍然想知道为什么它被删除了。
I thought it would be worthwhile writing something that does it best at guessing the defining class.我认为值得写一些最擅长猜测定义类的东西。 For completeness' sake this answer also addresses bound methods.为了完整起见,这个答案还涉及绑定方法。
At worst, guessing should fail altogether, with the function returning None
.在最坏的情况下,猜测应该完全失败,函数返回None
。 However, under any circumstances, it shouldn't raise an exception or return an incorrect class.但是,在任何情况下,它都不应该引发异常或返回不正确的类。
The final version of our function successfully overcomes most simple cases, and a few pitfalls as well.我们函数的最终版本成功地克服了大多数简单的情况,以及一些陷阱。
In a nutshell, its implementation differentiates between bound methods and “unbound methods“ (functions) since in Python 3
there is no reliable way to extract the enclosing class from an “unbound method".简而言之,它的实现区分了绑定方法和“未绑定方法”(函数),因为在Python 3
中没有可靠的方法从“未绑定方法”中提取封闭类。
MRO
, in a similar manner to that done in the accepted answer to an equivalent question for Python 2
.对于绑定方法,它只是遍历MRO
,其方式与Python 2
的等效问题的已接受答案中所做的方式类似。Python 3.3
and is quite reckless (if this feature is unnecessary it's probably best to remove this block of code and just return None
instead).对于“未绑定方法”,它依赖于解析其 限定名称,该 名称仅在Python 3.3
可用并且非常鲁莽(如果不需要此功能,最好删除此代码块并只返回None
代替)。Several useful comments prompted additional changes, as detailed in the edits section below, producing the following improvements:一些有用的评论提示了额外的更改,如下面的编辑部分所述,产生了以下改进:
set.union
, int.__add__
and int().__add__
) and for built-in methods (for example set().union
and io.BytesIO().__enter__
).对通过描述符定义的方法的有限处理,这些方法不属于普通方法或函数(例如set.union
、 int.__add__
和int().__add__
)和内置方法(例如set().union
和io.BytesIO().__enter__
)。functools.partial
objects. functools.partial
对象的处理。The resulting function is:结果函数是:
def get_class_that_defined_method(meth):
if isinstance(meth, functools.partial):
return get_class_that_defined_method(meth.func)
if inspect.ismethod(meth) or (inspect.isbuiltin(meth) and getattr(meth, '__self__', None) is not None and getattr(meth.__self__, '__class__', None)):
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
meth = getattr(meth, '__func__', meth) # fallback to __qualname__ parsing
if inspect.isfunction(meth):
cls = getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
if isinstance(cls, type):
return cls
return getattr(meth, '__objclass__', None) # handle special descriptor objects
If you decide to use this implementation, and encounter any caveats, please comment and describe what happened.如果您决定使用此实现,并遇到任何警告,请发表评论并描述发生的情况。
First of all, it's worth noting the following change made in Python 3
(see Guido's motivation here ):首先,值得注意的是Python 3
的以下更改(请参阅此处的Guido 动机):
The concept of “unbound methods” has been removed from the language. “未绑定方法”的概念已从语言中删除。 When referencing a method as a class attribute, you now get a plain function object.将方法作为类属性引用时,您现在会得到一个普通的函数对象。
This makes it practically impossible to reliably extract the class in which a certain “unbound method“ was defined unless it's bound to an object of that class (or of one of its subclasses).这使得实际上不可能可靠地提取定义了某个“未绑定方法”的类,除非它绑定到该类(或其子类之一)的对象。
So, let us first handle the “easier case“ in which we have a bound method.因此,让我们首先处理“更简单的情况”,其中我们有一个绑定方法。 Note that the bound method must be written in Python
, as described ininspect.ismethod
's documentation .请注意,绑定方法必须用Python
编写,如inspect.ismethod
的文档中所述。
def get_class_that_defined_method(meth):
# meth must be a bound method
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
return None # not required since None would have been implicitly returned anyway
However, this solution is not perfect and has its perils, as methods can be assigned in runtime, rendering their name possibly different than that of the attribute that they are assigned to (see example below).然而,这个解决方案并不完美并且有其危险,因为可以在运行时分配方法,使它们的名称可能与分配给它们的属性的名称不同(请参见下面的示例)。 This problem exists also in Python 2
.这个问题也存在于Python 2
。 A possible workaround would be to iterate over all of the class's attributes, looking for one whose identity is that of the specified method.一种可能的解决方法是遍历类的所有属性,寻找身份与指定方法相同的属性。
Now that we got that out of the way, we can suggest a hack that tries to handle “unbound methods”.现在我们已经解决了这个问题,我们可以建议一个尝试处理“未绑定方法”的黑客。 The hack, its rationale, and some discouragement words can be found in this answer .可以在这个答案中找到黑客,它的基本原理和一些劝阻词。 It relies on manually parsing the __qualname__
attribute , available only from Python 3.3
, is highly unrecommended, but should work for simple cases:它依赖于手动解析__qualname__
属性, 仅在Python 3.3
可用,非常不推荐,但应该适用于简单情况:
def get_class_that_defined_method(meth):
if inspect.isfunction(meth):
return getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
return None # not required since None would have been implicitly returned anyway
Since inspect.isfunction
and inspect.ismethod
are mutually exclusive, combining both approaches into a single solution gives us the following (with added logging facilities for the upcoming examples):由于inspect.isfunction
和inspect.ismethod
是相互排斥的,将这两种方法结合到一个解决方案中可以为我们提供以下内容(为即将到来的示例添加日志记录工具):
def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
print('this is a method')
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
if inspect.isfunction(meth):
print('this is a function')
return getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
print('this is neither a function nor a method')
return None # not required since None would have been implicitly returned anyway
>>> class A:
... def a(self): pass
...
>>> class B:
... def b(self): pass
...
>>> class C(A, B):
... def a(self): pass
...
>>> A.a
<function A.a at 0x7f13b58dfc80>
>>> get_class_that_defined_method(A.a)
this is a function
<class '__main__.A'>
>>>
>>> A().a
<bound method A.a of <__main__.A object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(A().a)
this is a method
<class '__main__.A'>
>>>
>>> C.a
<function C.a at 0x7f13b58dfea0>
>>> get_class_that_defined_method(C.a)
this is a function
<class '__main__.C'>
>>>
>>> C().a
<bound method C.a of <__main__.C object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(C().a)
this is a method
<class '__main__.C'>
>>>
>>> C.b
<function B.b at 0x7f13b58dfe18>
>>> get_class_that_defined_method(C.b)
this is a function
<class '__main__.B'>
>>>
>>> C().b
<bound method C.b of <__main__.C object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(C().b)
this is a method
<class '__main__.B'>
So far, so good, but...到目前为止,一切都很好,但是......
>>> def x(self): pass
...
>>> class Z:
... y = x
... z = (lambda: lambda: 1)() # this returns the inner function
... @classmethod
... def class_meth(cls): pass
... @staticmethod
... def static_meth(): pass
...
>>> x
<function x at 0x7f13b58dfa60>
>>> get_class_that_defined_method(x)
this is a function
<function x at 0x7f13b58dfa60>
>>>
>>> Z.y
<function x at 0x7f13b58dfa60>
>>> get_class_that_defined_method(Z.y)
this is a function
<function x at 0x7f13b58dfa60>
>>>
>>> Z().y
<bound method Z.x of <__main__.Z object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(Z().y)
this is a method
this is neither a function nor a method
>>>
>>> Z.z
<function Z.<lambda>.<locals>.<lambda> at 0x7f13b58d40d0>
>>> get_class_that_defined_method(Z.z)
this is a function
<class '__main__.Z'>
>>>
>>> Z().z
<bound method Z.<lambda> of <__main__.Z object at 0x7f13b58ca9e8>>
>>> get_class_that_defined_method(Z().z)
this is a method
this is neither a function nor a method
>>>
>>> Z.class_meth
<bound method type.class_meth of <class '__main__.Z'>>
>>> get_class_that_defined_method(Z.class_meth)
this is a method
this is neither a function nor a method
>>>
>>> Z().class_meth
<bound method type.class_meth of <class '__main__.Z'>>
>>> get_class_that_defined_method(Z().class_meth)
this is a method
this is neither a function nor a method
>>>
>>> Z.static_meth
<function Z.static_meth at 0x7f13b58d4158>
>>> get_class_that_defined_method(Z.static_meth)
this is a function
<class '__main__.Z'>
>>>
>>> Z().static_meth
<function Z.static_meth at 0x7f13b58d4158>
>>> get_class_that_defined_method(Z().static_meth)
this is a function
<class '__main__.Z'>
The outcome generated by x
and Zy
can be partially fixed (to return None
) by verifying that the returned value is a class, before actually returning it.通过在实际返回之前验证返回的值是一个类,可以部分修复x
和Zy
生成的结果(返回None
)。
The outcome generated by Z().z
can be fixed by falling back to parsing the function's __qualname__
attribute (the function can be extracted via meth.__func__
). Z().z
生成的结果可以通过回退到解析函数的__qualname__
属性来修复(该函数可以通过meth.__func__
提取)。
The outcome generated by Z.class_meth
and Z().class_meth
is incorrect because accessing a class method always returns a bound method, whose __self__
attribute is the class itself, rather than its object. Z.class_meth
和Z().class_meth
生成的结果是不正确的,因为访问类方法总是返回一个绑定方法,其__self__
属性是类本身,而不是它的对象。 Thus, further accessing the __class__
attribute on top of that __self__
attribute doesn't work as expected:因此,在__self__
属性之上进一步访问__class__
属性无法按预期工作:
>>> Z().class_meth <bound method type.class_meth of <class '__main__.Z'>> >>> Z().class_meth.__self__ <class '__main__.Z'> >>> Z().class_meth.__self__.__class__ <class 'type'>
This can be fixed by checking whether the method's __self__
attribute returns an instance of type
.这可以通过检查方法的__self__
属性是否返回type
的实例来解决。 However, this might be confusing when our function is invoked against methods of a metaclass, so we'll leave it as is for now.然而,当我们的函数被元类的方法调用时,这可能会令人困惑,所以我们暂时保持原样。
Here is the final version:这是最终版本:
def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
meth = meth.__func__ # fallback to __qualname__ parsing
if inspect.isfunction(meth):
cls = getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
if isinstance(cls, type):
return cls
return None # not required since None would have been implicitly returned anyway
Surprisingly, this also fixes the outcome of Z.class_meth
and Z().class_meth
which now correctly return Z
.令人惊讶的是,这也修复了Z.class_meth
和Z().class_meth
现在正确返回Z
。 This is because the __func__
attribute of a class method returns a regular function whose __qualname__
attribute may be parsed:这是因为类方法的__func__
属性返回一个常规函数,其__qualname__
属性可以被解析:
>>> Z().class_meth.__func__
<function Z.class_meth at 0x7f13b58d4048>
>>> Z().class_meth.__func__.__qualname__
'Z.class_meth'
EDIT:编辑:
As per the issue raised by Bryce , it's possible to handle method_descriptor
objects, like set.union
, and wrapper_descriptor
objects, like int.__add__
, merely by returning their __objclass__
attribute (introduced by PEP-252 ), if such exists:根据Bryce提出的问题,可以处理method_descriptor
对象,如set.union
和wrapper_descriptor
对象,如int.__add__
,只需返回它们的__objclass__
属性(由PEP-252引入),如果存在:
if inspect.ismethoddescriptor(meth):
return getattr(meth, '__objclass__', None)
However, inspect.ismethoddescriptor
returns False
for the respective instance method objects, ie for set().union
and for int().__add__
:但是, inspect.ismethoddescriptor
为各自的实例方法对象返回False
,即对于set().union
和int().__add__
:
int().__add__.__objclass__
returns int
, the above if clause may be relinquished in order to solve the problem for int().__add__
.由于int().__add__.__objclass__
返回int
,为了解决int().__add__
的问题,可以放弃上面的 if 子句。 Unfortunately, this doesn't address the matter of set().union
, for which no __objclass__
attribute is defined.不幸的是,这并没有解决set().union
,为此没有定义__objclass__
属性。 In order to avoid an AttributeError
exception in such a case, the __objclass__
attribute isn't accessed directly, but rather via the getattr
function.为了避免在这种情况下出现AttributeError
异常,不直接访问__objclass__
属性,而是通过getattr
函数访问。EDIT:编辑:
As per the issue raised by x-yuri , it seems that our function fails to handle the method io.BytesIO().__enter__
since inspect
doesn't identify it as a method, but rather as a built-in:根据x-yuri提出的问题,我们的函数似乎无法处理io.BytesIO().__enter__
方法,因为inspect
并未将其识别为方法,而是将其识别为内置方法:
>>> inspect.ismethod(io.BytesIO().__enter__)
False
>>> inspect.isbuiltin(io.BytesIO().__enter__)
True
This is the same issue encountered above in regard to set().union
:这与上面在set().union
遇到的问题相同:
>>> inspect.ismethod(set().union)
False
>>> inspect.isbuiltin(set().union)
True
Other than this peculiarity, we can handle such methods as ordinary methods and extract the defining class by traversing the MRO.除了这个特性,我们可以像处理普通方法一样处理这些方法,并通过遍历 MRO 来提取定义类。
However, just to be on the safe side, we shall include an extra layer of protection and verify that the __self__
attribute of such methods, if defined, isn't None
and that the __class__
attribute of that __self__
object, if defined, isn't None
as well:然而,为了安全起见,我们将包含一个额外的保护层,并验证这些方法的__self__
属性(如果已定义)不是None
并且该__self__
对象的__class__
属性(如果已定义)不是“ t 也None
:
if inspect.ismethod(meth) or (inspect.isbuiltin(meth) and getattr(meth, '__self__', None) and getattr(meth.__self__, '__class__', None)):
# ordinary method handling
Alas, this simple test fails for set().union
because bool(set().union.__self__)
evaluates to False
since set().union.__self__
returns the empty set.唉,这个简单的测试对set().union
失败了,因为bool(set().union.__self__)
评估为False
因为set().union.__self__
返回空集。 Thus, an explicit test against None
is required, producing the following fix:因此,需要对None
进行显式测试,从而产生以下修复:
if inspect.ismethod(meth) or (inspect.isbuiltin(meth) and getattr(meth, '__self__', None) is not None and getattr(meth.__self__, '__class__', None)):
# ordinary method handling
A minor additional patch is advised in order to avoid a possible AttributeError
exception when accessing the __func__
attribute during fallback to __qualname__
parsing.为了避免在回__qualname__
解析期间访问__func__
属性时可能出现的AttributeError
异常,建议添加一个小补丁。 This is required since while the __func__
attribute is guaranteed to exist for an ordinary method, it's not necessarily defined for one of the type builtin_function_or_method
, such as io.BytesIO().__enter__
and set().union
.这是必需的,因为虽然保证__func__
属性对于普通方法存在,但它不一定是为builtin_function_or_method
类型之一定义的,例如io.BytesIO().__enter__
和set().union
。
def get_class_that_defined_method(meth):
if inspect.ismethod(meth) or (inspect.isbuiltin(meth) and getattr(meth, '__self__', None) is not None and getattr(meth.__self__, '__class__', None)):
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
meth = getattr(meth, '__func__', meth) # fallback to __qualname__ parsing
if inspect.isfunction(meth):
cls = getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
if isinstance(cls, type):
return cls
return getattr(meth, '__objclass__', None) # handle special descriptor objects
EDIT:编辑:
As per the suggestion put forward by user1956611 , it's possible to handle partial
objects by introducing a recursive call to seek out the original callable with which the partial
object was created:根据user1956611提出的建议,可以通过引入递归调用来查找创建partial
对象的原始可调用对象来处理partial
对象:
if isinstance(meth, functools.partial):
return get_class_that_defined_method(meth.func)
The point you appear to be missing is, in Python 3 the "unbound method" type has entirely disappeared -- a method, until and unless it's bound, is just a function, without the weird "type-checking" unbound methods used to perform.您似乎缺少的一点是,在 Python 3 中,“未绑定方法”类型已经完全消失了——一个方法,直到并且除非它被绑定,否则只是一个函数,没有用于执行的奇怪的“类型检查”未绑定方法. This makes the language simpler!这使语言更简单!
To wit...:以机智...:
>>> class X:
... def Y(self): pass
...
>>> type(X.Y)
<class 'function'>
and voila -- one less subtle concept and distinction to worry about.瞧——一个不那么微妙的概念和区别需要担心。 Such simplifications are the core advantage of Python 3 wrt Python 2, which (over the years) had been accumulating so many subtleties that it was in danger (if features kept being added to it) of really losing its status as a simple language.这种简化是 Python 3 与 Python 2 相比的核心优势,Python 2(多年来)积累了如此多的微妙之处,以至于它面临着真正失去其作为简单语言的地位的危险(如果功能不断添加到其中)。 With Python 3, simplicity is back !-)使用 Python 3,简单又回来了!-)
Since python 3.6 you could accomplish what you are describing using a decorator that defines a __set_name__
method.从 python 3.6 开始,您可以使用定义__set_name__
方法的装饰器来完成您所描述的__set_name__
。 The documentation states that object.__set_name__
is called when the class is being created. 该文档指出object.__set_name__
在创建类时被调用。
Here is an example that decorates a method "in order to register it in a list of methods that serve a particular purpose":这是一个装饰方法的示例,“以便将其注册到服务于特定目的的方法列表中”:
>>> class particular_purpose:
... def __init__(self, fn):
... self.fn = fn
...
... def __set_name__(self, owner, name):
... owner._particular_purpose.add(self.fn)
...
... # then replace ourself with the original method
... setattr(owner, name, self.fn)
...
... class A:
... _particular_purpose = set()
...
... @particular_purpose
... def hello(self):
... return "hello"
...
... @particular_purpose
... def world(self):
... return "world"
...
>>> A._particular_purpose
{<function __main__.A.hello(self)>, <function __main__.A.world(self)>}
>>> a = A()
>>> for fn in A._particular_purpose:
... print(fn(a))
...
world
hello
Note that this question is very similar to Can a Python decorator of an instance method access the class?请注意,这个问题与Can a Python decorator of an instance method access the class非常相似? and therefore my answer as well to the answer I provided there .因此我的回答以及我在那里提供的答案。
A small extension for python 3.6 (python 2.7 worked fine) to the great answer of https://stackoverflow.com/a/25959545/4013571 python 3.6的一个小扩展(python 2.7工作正常)对https://stackoverflow.com/a/25959545/4013571的很好的回答
def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if cls.__dict__.get(meth.__name__) is meth:
return cls
meth = meth.__func__ # fallback to __qualname__ parsing
if inspect.isfunction(meth):
class_name = meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0]
try:
cls = getattr(inspect.getmodule(meth), class_name)
except AttributeError:
cls = meth.__globals__.get(class_name)
if isinstance(cls, type):
return cls
return None # not required since None would have been implicitly returned anyway
I found the following adjustment was required for doctest
我发现doctest
需要进行以下调整
except AttributeError:
cls = meth.__globals__.get(class_name)
As for some reason, when using nose
the inspect.getmodule(meth)
didn't contain the defining class由于某种原因,当使用nose
, inspect.getmodule(meth)
不包含定义类
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.