Get defining class of unbound method object in Python 3
假设我想为类中定义的方法制作一个修饰器。我希望这个修饰器在被调用时能够在定义方法的类上设置一个属性(以便将它注册到一个特定用途的方法列表中)。
在python2中,
1 2 3 4 | def decorator(method): cls = method.im_class cls.foo = 'bar' return method |
然而,在python3中,似乎不存在这样的属性(或它的替换)。我想您可以调用
注意:这个问题实际上与我的情况有点不相关,因为我选择了在方法本身上设置一个属性,然后让实例在适当的时候扫描它的所有方法以查找该属性。我(目前)也在使用python2.6。但是,我很好奇是否有版本2功能的替代品,如果没有,完全删除它的理由是什么。
编辑:我刚刚发现这个问题。这让我觉得最好的解决办法就是像我一样避免它。不过,我还是想知道为什么要把它取下来。
我认为写些最适合猜测定义类的东西是值得的。为了完整起见,这个答案还涉及绑定方法。好的。
最坏情况下,猜测应该完全失败,函数返回
我们的函数的最终版本成功地克服了最简单的情况,以及一些陷阱。好的。
简而言之,它的实现区分了绑定方法和"未绑定方法"(函数),因为在
- 对于有界方法,它简单地遍历
MRO ,其方式与在接受的Python 2 等价问题的答案中所做的方式类似。 - 对于"未绑定方法",它依赖于分析其限定名,该限定名只能从
Python 3.3 获得,而且非常鲁莽(如果不需要此功能,最好删除此代码块,然后返回None )。
对于通过描述符定义的方法,也有部分处理,这些方法没有被归类为普通的方法或函数(例如,
结果函数为:好的。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
小小的要求
如果您决定使用这个实现,并且遇到任何警告,请评论并描述发生了什么。好的。完整版本"未绑定方法"是正则函数
首先,值得注意的是,在
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.
Ok.
这使得几乎不可能可靠地提取在其中定义了某个"未绑定方法"的类,除非它绑定到该类(或其子类之一)的对象。好的。处理绑定方法
因此,让我们首先处理"更简单的情况",其中我们有一个绑定方法。注意,绑定方法必须写在
1 2 3 4 5 6 7 | 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 |
但是,此解决方案并不完美,而且存在风险,因为可以在运行时分配方法,从而使它们的名称可能与分配给它们的属性的名称不同(请参见下面的示例)。这个问题也存在于
既然我们已经排除了这一点,我们可以建议一个尝试处理"未绑定方法"的黑客。在这个答案中可以找到黑客,它的基本原理和一些令人沮丧的话。它依赖于手动分析
1 2 3 4 5 | 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 |
结合两种方法
由于
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
执行示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | >>> 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'> |
到目前为止,很好,但是…好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | >>> 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'> |
最后的接触
Z.y 生成的结果可以部分固定(返回None ,方法是在实际返回之前验证返回值是一个类。- 通过对函数的
__qualname__ 属性进行分析,可以修复Z().z 生成的结果(可以通过meth.__func__ 提取函数)。 Z.class_meth 和Z().class_meth 生成的结果不正确,因为访问类方法总是返回一个绑定方法,其__self__ 属性是类本身,而不是其对象。因此,进一步访问该__self__ 属性之上的__class__ 属性并不能按预期工作:好的。1
2
3
4
5
6>>> 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 的实例来解决。但是,当针对元类的方法调用我们的函数时,这可能会令人困惑,因此现在我们将保持原样。好的。
以下是最终版本:好的。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
令人惊讶的是,这也修正了
1 2 3 4 | >>> Z().class_meth.__func__ <function Z.class_meth at 0x7f13b58d4048> >>> Z().class_meth.__func__.__qualname__ 'Z.class_meth' |
编辑:好的。
根据Bryce提出的问题,仅通过返回
1 2 | if inspect.ismethoddescriptor(meth): return getattr(meth, '__objclass__', None) |
但是,对于各自的实例方法对象,即对于
- 由于
int().__add__.__objclass__ 返回int ,为了解决int().__add__ 的问题,可以放弃上述if子句。不幸的是,这并不能解决没有定义__objclass__ 属性的set().union 问题。为了避免在这种情况下出现AttributeError 异常,不直接访问__objclass__ 属性,而是通过getattr 函数访问。
好啊。
您似乎缺少的一点是,在python 3中,"unbound method"类型已经完全消失了——一个方法,直到并且除非它被绑定,它只是一个函数,没有用于执行的奇怪的"类型检查"未绑定方法。这使语言更简单!
为了……
1 2 3 4 5 | >>> class X: ... def Y(self): pass ... >>> type(X.Y) <class 'function'> |
瞧——一个不那么微妙的概念和区别值得担心。这种简化是python 3与python 2的核心优势,多年来,python 3积累了太多的微妙之处,以至于它面临着真正失去简单语言地位的危险(如果不断添加特性的话)。有了Python3,简单性又回来了!-)
由于python 3.6,您可以使用定义
下面是一个示例,它修饰了一个方法"以便将其注册到一个具有特定用途的方法列表中":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | >>> 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修饰符可以访问类吗?因此,我的答案和我在那里提供的答案一样。
python 3.6(python 2.7)的一个小扩展可以很好地解决https://stackoverflow.com/a/25959545/4013571的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
我发现
1 2 | except AttributeError: cls = meth.__globals__.get(class_name) |
由于某种原因,当使用