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

2025-02-18 09:24:00
admin
原创
29
摘要:问题描述:假设我想为类中定义的方法创建一个装饰器。我希望该装饰器在调用时能够设置定义该方法的类的属性(以便将其注册到用于特定目的的方法列表中)。在 Python 2 中,该im_class方法很好地实现了这一点:def decorator(method): cls = method.im_class ...

问题描述:

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

在 Python 2 中,该im_class方法很好地实现了这一点:

def decorator(method):
  cls = method.im_class
  cls.foo = 'bar'
  return method

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

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

编辑:我刚刚发现了这个问题。这似乎最好的解决方案就是像我一样避免它。不过我仍然想知道为什么它被删除了。


解决方案 1:

我认为编写一些能够最好地猜测定义类的东西是值得的。为了完整起见,这个答案还涉及绑定方法。

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

总结

我们的函数的最终版本成功克服了大多数简单情况,以及一些陷阱。

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

  • 对于绑定方法,它只是遍历,其方式与对的等效问题的已接受答案MRO中所做的类似。Python 2

  • 对于“未绑定方法”,它依赖于解析其限定名称,该名称仅可从获得Python 3.3并且非常鲁莽(如果不需要此功能,最好删除此代码块并直接返回None)。

一些有用的评论促使我们进行了额外的修改,如下面的编辑部分所述,并产生了以下改进:

  • 对于通过描述符定义的方法(不属于普通方法或函数,例如set.union和)以及内置方法(例如和)int.__add__的处理有限。int().__add__`set().union`io.BytesIO().__enter__

  • 物体的处理functools.partial

得到的函数是:

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

一个小请求

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


完整版

“未绑定方法”是常规函数

首先,值得注意的是以下所做的更改(请参阅此处的Python 3Guido 的动机):

“未绑定方法”的概念已从语言中删除。当将方法作为类属性引用时,您现在会获得一个普通函数对象。

这使得可靠地提取定义了某个“未绑定方法”的类几乎不可能,除非该方法绑定到该类(或其子类之一)的对象。

处理绑定方法

因此,我们首先处理“更简单的情况”,即我们有一个绑定方法。请注意,绑定方法必须以 编写,如的文档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

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

处理“未绑定方法”

现在我们已经解决了这个问题,我们可以提出一个尝试处理“未绑定方法”的 hack。这个 hack、其基本原理和一些劝阻的话可以在这个答案中找到。它依赖于手动解析属性,只能从中__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

结合两种方法

由于inspect.isfunctioninspect.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'>

到目前为止一切顺利,但是...

>>> 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'>

最后的润色

  • 在实际返回之前,可以通过验证返回值是否是一个类来部分修复x和生成的结果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'>

可以通过检查方法的__self__属性是否返回 的实例来修复此问题type。但是,当我们的函数针对元类的方法调用时,这可能会造成混淆,因此我们暂时保持原样。

最终版本如下:

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

Z.class_meth令人惊讶的是,这也修复了和的结果,Z().class_meth现在正确返回Z。这是因为__func__类方法的属性返回一个常规函数,其__qualname__属性可以解析:

>>> Z().class_meth.__func__
<function Z.class_meth at 0x7f13b58d4048>
>>> Z().class_meth.__func__.__qualname__
'Z.class_meth'

编辑:

根据Bryce提出的问题,可以处理method_descriptor诸如 之类的对象set.union,以及wrapper_descriptor之类的对象int.__add__,仅通过返回它们的__objclass__属性(由PEP-252引入),如果存在的话:

if inspect.ismethoddescriptor(meth):
    return getattr(meth, '__objclass__', None)

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

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


编辑:

根据x-yuri提出的问题,似乎我们的函数无法处理该方法,因为没有将其标识为方法,而是将其标识为内置方法:io.BytesIO().__enter__`inspect`

>>> inspect.ismethod(io.BytesIO().__enter__)
False
>>> inspect.isbuiltin(io.BytesIO().__enter__)
True

这与上面遇到的问题是相同的set().union

>>> inspect.ismethod(set().union)
False
>>> inspect.isbuiltin(set().union)
True

除了这个特殊性之外,我们可以将这些方法视为普通方法,并通过遍历 MRO 来提取定义类。

但是,为了安全起见,我们应该添加一层额外的保护,并验证__self__此类方法的属性(如果已定义)是否已定义None,以及__class__该对象的属性__self__(如果已定义)是否也已定义None

if inspect.ismethod(meth) or (inspect.isbuiltin(meth) and getattr(meth, '__self__', None) and getattr(meth.__self__, '__class__', None)):
    # ordinary method handling

可惜的是,这个简单的测试失败了,set().union因为bool(set().union.__self__)计算结果为,False因为set().union.__self__返回空集。因此,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

建议进行一次小规模的额外修补,以避免在回退到解析期间AttributeError访问属性时可能出现的异常。这是必需的,因为虽然保证属性对于普通方法存在,但它不一定为类型之一定义,例如和。__func__`__qualname____func__builtin_function_or_methodio.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

编辑:

根据user1956611提出的建议,可以通过引入递归调用来查找创建对象的原始可调用函数来处理对象:partial`partial`

if isinstance(meth, functools.partial):
    return get_class_that_defined_method(meth.func)

解决方案 2:

您似乎忽略了一点,在 Python 3 中,“未绑定方法”类型已完全消失 - 方法,除非被绑定,否则只是一个函数,没有执行未绑定方法所执行的奇怪的“类型检查”。这使得语言更简单!

即...:

>>> class X:
...   def Y(self): pass
... 
>>> type(X.Y)
<class 'function'>

瞧——少了一个需要担心的微妙概念和区别。这种简化是 Python 3 相对于 Python 2 的核心优势,多年来,Python 2 已经积累了如此多的微妙之处,以至于它有失去简单语言地位的危险(如果不断添加功能的话)有了 Python 3,简单又回来了!-)

解决方案 3:

从 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

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

解决方案 4:

针对https://stackoverflow.com/a/25959545/4013571的出色答案的Python 3.6的小扩展(Python 2.7 运行良好)

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

我发现需要进行以下调整doctest

        except AttributeError:
            cls = meth.__globals__.get(class_name)

由于某种原因,使用时不包含定义noseinspect.getmodule(meth)

解决方案 5:

我制作了一个基本的包装器对象,它包装了一个函数并包含有关该函数的父类和模块的信息。

我已经在Yoel 的回答中包含了“get_class_that_defined_method”作为获取类信息的方式。

这是该文件的 GitHub 要点的链接。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1324  
  IPD研发管理体系作为一种先进的研发管理理念和方法,对于打造优质产品体验起着至关重要的作用。它涵盖了从产品规划、研发、上市到生命周期管理的全流程,通过整合资源、优化流程、加强团队协作等方式,确保产品能够精准满足用户需求,提升用户满意度和忠诚度。IPD研发管理体系的核心原则IPD研发管理体系以市场驱动为核心原则。这意味着...
IPD集成产品开发   8  
  IPD(Integrated Product Development)产品开发流程作为一种先进的产品开发管理模式,在众多企业中得到广泛应用。它强调跨部门团队协作、并行工程以及基于市场的产品开发理念,旨在提高产品开发效率、缩短产品上市时间、提升产品质量。而成本控制在产品开发过程中至关重要,关乎企业的利润空间和市场竞争力。...
华为IPD流程   6  
  IPD(Integrated Product Development)产品开发流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。它从多个维度对产品开发过程进行优化和整合,为企业创新提供了强大的支撑。通过实施IPD产品开发流程,企业能够更加高效地将创意转化为具有市场竞争力的产品,从而在激烈的市场竞争中占据优...
华为IPD流程管理   10  
  华为作为全球知名的科技企业,其产品质量在市场上有口皆碑。华为IPD产品开发流程在确保产品质量方面发挥了至关重要的作用。IPD(Integrated Product Development)即集成产品开发,是一套先进的、成熟的产品开发管理思想、模式和方法。它打破了传统产品开发中各部门之间的壁垒,强调跨部门团队协作,从产品...
IPD集成产品开发流程   9  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用