在Python 3中获取未绑定方法对象的定义类

Tim*_*tes 35 python python-3.x

假设我想为类中定义的方法创建装饰器.我希望装饰器在被调用时能够在定义方法的类上设置属性(以便将其注册到用于特定目的的方法列表中).

在Python 2中,该im_class方法很好地完成了这个:

def decorator(method):
  cls = method.im_class
  cls.foo = 'bar'
  return method
Run Code Online (Sandbox Code Playgroud)

但是,在Python 3中,似乎不存在这样的属性(或替代它).我想这个想法是你可以调用type(method.__self__)来获取类,但是这对于未绑定的方法不起作用,因为__self__ == None在那种情况下.

注意:这个问题实际上与我的情况有点无关,因为我选择在方法本身上设置属性,然后让实例扫描其所有方法,在适当的时间查找该属性.我(目前)也在使用Python 2.6.但是,我很好奇是否有替换版本2的功能,如果没有,那么完全删除它的理由是什么.

编辑:我刚发现这个问题.这使得看起来最好的解决方案就是像我一样避免它.我仍然想知道为什么它被删除了.

Yoe*_*oel 69

我认为写一些在猜测定义类时做得最好的东西是值得的.为了完整起见,这个答案也解决了绑定方法.

在最坏的情况下,猜测应该完全失败,函数返回None.但是,在任何情况下,都不应该引发异常或返回错误的类.

TL; DR

我们函数的最终版本成功地克服了大多数简单案例,以及一些陷阱.

简而言之,它的实现区分了绑定方法和"未绑定方法"(函数),因为Python 3没有可靠的方法从"未绑定方法"中提取封闭类.

还存在用于通过描述符中定义的方法,没有被分类为常规方法或功能的部分的处理(例如,set.union,int.__add__int().__add__,但不是set().union).

由此产生的功能是:

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):
        cls = getattr(inspect.getmodule(meth),
                      meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
        if isinstance(cls, type):
            return cls
    return getattr(meth, '__objclass__', None)  # handle special descriptor objects
Run Code Online (Sandbox Code Playgroud)

一个小要求

如果您决定使用此实现,并遇到任何警告,请评论并描述发生的情况.


完整版

"未绑定方法"是常规功能

首先,值得注意的以下变化中所作Python 3(见圭多的动机在这里):

"未绑定方法"的概念已从语言中删除.将方法作为类属性引用时,现在可以获得普通的函数对象.

这使得实际上不可能可靠地提取其中定义了某个"未绑定方法"的类,除非它绑定到该类(或其子类之一)的对象.

处理绑定方法

那么,让我们首先处理我们有绑定方法的"更容易的情况".请注意,必须写入绑定方法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 cls.__dict__.get(meth.__name__) is meth:
                return cls
    return None  # not required since None would have been implicitly returned anyway
Run Code Online (Sandbox Code Playgroud)

但是,这个解决方案并不完美并且有其危险,因为可以在运行时分配方法,使它们的名称可能与它们所分配的属性的名称不同(参见下面的示例).这个问题也存在于Python 2.一种可能的解决方法是迭代所有类的属性,寻找其身份是指定方法的属性.

处理"未绑定的方法"

现在我们已经解决了这个问题,我们可以建议一个试图处理"未绑定方法"的黑客.在这个答案中可以找到黑客,其理由和一些沮丧的话.它依赖于人工分析__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])
    return None  # not required since None would have been implicitly returned anyway
Run Code Online (Sandbox Code Playgroud)

结合两种方法

由于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 cls.__dict__.get(meth.__name__) is meth:
                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])
    print('this is neither a function nor a method')
    return None  # not required since None would have been implicitly returned anyway
Run Code Online (Sandbox Code Playgroud)

执行示例

>>> 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'>
Run Code Online (Sandbox Code Playgroud)

到目前为止,这么好,但......

>>> 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
...
>>> 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'>
Run Code Online (Sandbox Code Playgroud)

最后的接触

  • 生成的结果可以通过在实际返回之前验证返回的值是一个类Z.y来部分修复(返回None).
  • 生成的结果Z().z可以通过回退到解析函数的__qualname__属性来修复(函数可以通过提取meth.__func__).
  • 由于访问类方法而生成的结果Z.class_meth并且Z().class_meth不正确,因为访问类方法始终返回绑定方法,该方法的__self__属性是类本身,而不是其对象.因此,进一步访问该__class__属性之上的__self__属性不会按预期方式工作:

    >>> 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'>
    
    Run Code Online (Sandbox Code Playgroud)

    这可以通过检查方法的__self__属性是否返回实例来修复type.但是,当我们的函数针对元类的方法调用时,这可能会让人感到困惑,因此我们暂时保留它.

这是最终版本:

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):
        cls = getattr(inspect.getmodule(meth),
                      meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0])
        if isinstance(cls, type):
            return cls
    return None  # not required since None would have been implicitly returned anyway
Run Code Online (Sandbox Code Playgroud)

出人意料的是,这还修复的结果Z.class_methZ().class_meth现在正确返回Z.这是因为__func__类方法的属性返回一个__qualname__可以解析其属性的常规函数:

>>> Z().class_meth.__func__
<function Z.class_meth at 0x7f13b58d4048>
>>> Z().class_meth.__func__.__qualname__
'Z.class_meth'
Run Code Online (Sandbox Code Playgroud)

编辑:

根据Bryce提出的问题,仅仅通过返回它们的属性(由PEP-252引入)来处理method_descriptor对象,比如set.unionwrapper_descriptor对象,如果存在的话:int.__add____objclass__

if inspect.ismethoddescriptor(meth):
    return getattr(meth, '__objclass__', None)
Run Code Online (Sandbox Code Playgroud)

但是,inspect.ismethoddescriptor返回False相应实例方法对象,即for set().union和for int().__add__:

  • 由于int().__add__.__objclass__返回int,可以放弃上面的if子句以解决问题int().__add__.不幸的是,这并没有解决set().union没有__objclass__定义任何属性的问题.为了避免AttributeError在这种情况下发生异常,__objclass__不直接访问该属性,而是通过该getattr函数访问该属性.


Ale*_*lli 39

你似乎缺少的一点是,在Python 3中,"未绑定方法"类型已经完全消失了 - 一个方法,除非它被绑定,只是一个函数,没有用于执行的奇怪的"类型检查"未绑定方法.这使语言更简单!

以机智...:

>>> class X:
...   def Y(self): pass
... 
>>> type(X.Y)
<class 'function'>
Run Code Online (Sandbox Code Playgroud)

瞧 - 一个不那么微妙的概念和区别值得担心.这种简化是Python 3与Python 2的核心优势,它(多年来)已经累积了如此多的微妙之处,以至于它们处于危险之中(如果功能一直被添加到其中)真正失去了它作为一种简单语言的地位.使用Python 3,简单性又回来了! - )

  • 那讲得通.我甚至没有意识到第一个参数类型检查等方法有额外的功能 - 我只是认为它们是具有一些额外属性的普通函数.有了这个,我完全可以理解删除. (2认同)
  • @Tim,是的,检查第一个参数是`.im_class`的实例(当然包括它的子类)是为什么`im_class`保留在第一位(并且同一层的间接性包裹在函数中)制作未绑定的方法,因为需要将其包装以制作绑定的方法).底层函数总是(并且仍然是_bound_方法)`.im_func`,BTW,永远不是方法对象本身. (2认同)
  • "使用Python 3,简单性又回来了!" 听起来它会制作一个好的保险杠贴纸(或笔记本电脑贴纸). (2认同)
  • 有人会介意解释这回答蒂姆的问题吗?谢谢! (2认同)

tyr*_*ion 5

从python 3.6开始,您可以使用定义__set_name__方法的装饰器来完成您所描述的内容。文档指出object.__set_name__在创建类时会调用该文档

这是一个装饰方法的示例,“以便将其注册到用于特定目的的方法列表中”:

>>> 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
Run Code Online (Sandbox Code Playgroud)

请注意,这个问题与实例方法的Python装饰器可以访问该类非常相似吗?因此,我的答案也是我在那里提供的答案