在Python3.1中,如何在类构造过程中找到绑定方法的类?

How to find class of bound method during class construction in Python 3.1?

我想编写一个修饰器,使类的方法对其他方可见;但是,我描述的问题独立于这个细节。代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
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()看到这个方法的时候,它还不能被调用;相反,它看到了它的未绑定版本。也许这可以通过在类上使用另一个修饰符来解决,该修饰符将负责处理必须对绑定方法执行的任何操作。接下来我要做的就是在修饰方法通过修饰器时用一个属性简单地指定它,然后使用类修饰器或元类进行后处理。如果我能做到这一点,那么我就不必解这个谜了,它仍然困扰着我:

在上面的代码中,任何人都可以在CLASS_WHERE_METHOD_IS_DEFINED下填写有意义的行,这样装饰器就可以在定义f时打印出它定义的类了吗?或者在python 3中排除了这种可能性?


当调用decorator时,它是以一个函数作为参数来调用的,而不是以一个方法来调用的——因此,如果decorator想检查和内省它的方法,那么它将毫无用处,因为它只是一个函数,不包含任何关于封闭类的信息。我希望这能解决你的"谜语",尽管是负面的!

可能会尝试其他方法,例如对嵌套堆栈帧进行深入的自省,但这些方法非常简单、脆弱,而且肯定不会延续到pynie等python3的其他实现中;因此,我强烈建议避免使用这些方法,以支持您已经在考虑的类装饰器解决方案,并且更清晰、更具Soli性。d.


正如我在其他一些答案中提到的,由于python3.6,这个问题的解决方案非常简单,这要归功于object.__set_name__,它是用正在定义的类对象调用的。

我们可以使用它来定义一个可以通过以下方式访问类的decorator:

1
2
3
4
5
6
7
8
9
10
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)

然后可以用作普通的装饰:

1
2
3
4
5
6
7
8
>>> 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)>


这是一篇很老的文章,但是内省不是解决这个问题的方法,因为使用元类和一些使用描述符的聪明的类构造逻辑可以更容易地解决这个问题。

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
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_selfinit_cls方法向它们传递有关它们所涉及的类的信息。

仅对定义了描述符的类调用init_self。这就是应该对foobar进行修改的地方,因为该方法只被调用一次。同时,对所有可以访问修饰方法的类调用init_cls。在这里可以引用对类foobar的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

例子

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

print(Y().f(3))
# PRINTS:
# 10