如何在Python 3.1类构造期间找到绑定方法的类?
我想写一个装饰器,让类的方法可以被其他地方看到。不过,我要说的问题和这个细节没有关系。代码大概是这样的:
def CLASS_WHERE_METHOD_IS_DEFINED( method ):
???
def foobar( method ):
print( CLASS_WHERE_METHOD_IS_DEFINED( method ) )
class X:
@foobar
def f( self, x ):
return x ** 2
我在这里遇到的问题是,当装饰器 foobar()
看到这个方法的时候,它还不能被调用;它看到的是一个未绑定的版本。也许可以通过在类上使用另一个装饰器来解决这个问题,这样可以处理绑定方法所需的事情。接下来我打算做的是,在方法通过装饰器时给它加一个属性,然后使用类装饰器或 metaclass 来进行后续处理。如果我能做到这一点,那我就不需要解决这个让我困惑的难题了:
有没有人能在上面的代码中填充 CLASS_WHERE_METHOD_IS_DEFINED
下的有意义的代码行,这样装饰器就能在 f
被定义的瞬间打印出它所在的类?或者在 Python 3 中,这种可能性是被排除的?
3 个回答
这是一篇很老的帖子,不过用反射来解决这个问题并不是最佳方法,因为其实可以通过一个元类和一些巧妙的类构造逻辑,结合描述符来更简单地解决。
import types
# a descriptor as a decorator
class foobar(object):
owned_by = None
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# a proxy for `func` that gets used when
# `foobar` is referenced from by a class
return self.func(*args, **kwargs)
def __get__(self, inst, cls=None):
if inst is not None:
# return a bound method when `foobar`
# is referenced from by an instance
return types.MethodType(self.func, inst, cls)
else:
return self
def init_self(self, name, cls):
print("I am named '%s' and owned by %r" % (name, cls))
self.named_as = name
self.owned_by = cls
def init_cls(self, cls):
print("I exist in the mro of %r instances" % cls)
# don't set `self.owned_by` here because
# this descriptor exists in the mro of
# many classes, but is only owned by one.
print('')
让这个方法有效的关键在于元类——它会在创建的类中查找定义的属性,寻找foobar
描述符。一旦找到,它就会通过描述符的init_self
和init_cls
方法,将相关类的信息传递给它们。
init_self
只会在定义了描述符的类上被调用。这是修改foobar
的地方,因为这个方法只会被调用一次。而init_cls
则会在所有可以访问被装饰方法的类上被调用。这是应该修改类foobar
的地方。
import inspect
class MetaX(type):
def __init__(cls, name, bases, classdict):
# The classdict contains all the attributes
# defined on **this** class - no attribute in
# the classdict is inherited from a parent.
for k, v in classdict.items():
if isinstance(v, foobar):
v.init_self(k, cls)
# getmembers retrieves all attributes
# including those inherited from parents
for k, v in inspect.getmembers(cls):
if isinstance(v, foobar):
v.init_cls(cls)
示例
# for compatibility
import six
class X(six.with_metaclass(MetaX, object)):
def __init__(self):
self.value = 1
@foobar
def f(self, x):
return self.value + x**2
class Y(X): pass
# PRINTS:
# I am named 'f' and owned by <class '__main__.X'>
# I exist in the mro of <class '__main__.X'> instances
# I exist in the mro of <class '__main__.Y'> instances
print('CLASS CONSTRUCTION OVER\n')
print(Y().f(3))
# PRINTS:
# 10
正如我在一些其他 回答中提到的,从Python 3.6开始,解决这个问题变得非常简单,这要归功于object.__set_name__
这个特性,它会在定义类的时候被调用。
我们可以用它来定义一个装饰器,这个装饰器可以访问到类,方法如下:
class class_decorator:
def __init__(self, fn):
self.fn = fn
def __set_name__(self, owner, name):
# do something with "owner" (i.e. the class)
print(f"decorating {self.fn} and using {owner}")
# then replace ourself with the original method
setattr(owner, name, self.fn)
然后我们就可以像使用普通装饰器一样使用它:
>>> class A:
... @class_decorator
... def hello(self, x=42):
... return x
...
decorating <function A.hello at 0x7f9bedf66bf8> and using <class '__main__.A'>
>>> A.hello
<function __main__.A.hello(self, x=42)>
当装饰器被调用时,它接收到的是一个函数作为参数,而不是一个方法。因此,装饰器想要检查和了解这个方法是没用的,因为它只是一段函数代码,根本没有关于它所在类的任何信息。我希望这能解答你的“谜题”,虽然是从反面来讲的!
你可以尝试其他方法,比如深入检查嵌套的调用栈,但这些方法都比较复杂、不稳定,而且在其他Python 3的实现(比如pynie)中也不一定能用。因此,我强烈建议你还是使用你已经考虑的类装饰器方案,这样更简洁、更可靠。