简体   繁体   English

如何在Python 3.1中的类构造中找到绑定方法的类?

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

i want to write a decorator that enables methods of classes to become visible to other parties; 我想写一个装饰器,使类的方法可以被其他方看到; the problem i am describing is, however, independent of that detail. 然而,我所描述的问题与该细节无关。 the code will look roughly like this: 代码看起来大致如下:

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

my problem here is that the very moment that the decorator, foobar() , gets to see the method, it is not yet callable; 我的问题在于装饰器foobar()看到方法的那一刻,它还无法调用; instead, it gets to see an unbound version of it. 相反,它可以看到它的未绑定版本。 maybe this can be resolved by using another decorator on the class that will take care of whatever has to be done to the bound method. 也许这可以通过在类上使用另一个装饰器来解决,该装饰器将处理对绑定方法必须做的任何事情。 the next thing i will try to do is to simply earmark the decorated method with an attribute when it goes through the decorator, and then use a class decorator or a metaclass to do the postprocessing. 我将尝试做的下一件事是在装饰方法通过装饰器时简单地使用属性标记,然后使用类装饰器或元类来进行后处理。 if i get that to work, then i do not have to solve this riddle, which still puzzles me: 如果我得到这个工作,那么我不必解决这个谜语,这仍然让我困惑:

can anyone, in the above code, fill out meaningful lines under CLASS_WHERE_METHOD_IS_DEFINED so that the decorator can actually print out the class where f is defined, the moment it gets defined? 在上面的代码中,任何人都可以在CLASS_WHERE_METHOD_IS_DEFINED下填写有意义的行,这样装饰器实际上可以打印出定义了f的类,它定义的那一刻? or is that possibility precluded in python 3? 或者在python 3中排除了这种可能性?

When the decorator is called, it's called with a function as its argument, not a method -- therefore it will avail nothing to the decorator to examine and introspect its method as much as it wants to, because it's only a function and carries no information whatsoever about the enclosing class. 当装饰器被调用时,它被调用一个函数作为它的参数, 而不是一个方法 - 因此它不会对装饰器进行任何检查和内省它的方法,因为它只是一个函数并且不携带任何信息关于封闭阶级的任何内容。 I hope this solves your "riddle", although in the negative sense! 我希望这能解决你的“谜语”,尽管在消极意义上!

Other approaches might be tried, such as deep introspection on nested stack frames, but they're hacky, fragile, and sure not to carry over to other implementations of Python 3 such as pynie; 可能会尝试其他方法,例如对嵌套堆栈帧的深入内省,但它们很容易破解,并且肯定不会延续到Python 3的其他实现,例如pynie; I would therefore heartily recommend avoiding them, in favor of the class-decorator solution that you're already considering and is much cleaner and more solid. 因此,我衷心建议避免使用它们,支持您已经考虑过的类装饰解决方案,并且更清洁,更稳固。

This is a very old post, but introspection isn't the way to solve this problem, because it can be more easily solved with a metaclass and a bit of clever class construction logic using descriptors . 这是一个非常古老的帖子,但内省不是解决这个问题的方法,因为使用元类和使用描述符的一些聪明的类构造逻辑可以更容易地解决它。

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

The key to making this work is the metaclass - it searches through the attributes defined on the classes it creates to find foobar descriptors. 使这项工作的关键是元类 - 它搜索它创建的类上定义的属性以查找foobar描述符。 Once it does, it passes them information about the classes they are involved in through the descriptor's init_self and init_cls methods. 一旦完成,它会通过描述符的init_selfinit_cls方法向它们传递有关它们所涉及的类的信息。

init_self is called only for the class which the descriptor is defined on. init_self仅针对定义描述符的类调用。 This is where modifications to foobar should be made, because the method is only called once. 这是对foobar进行修改的地方,因为该方法只被调用一次。 While init_cls is called for all classes which have access to the decorated method. 为所有可以访问装饰方法的类调用init_cls This is where modifications to the classes foobar can be referenced by should be made. 这是应该引用对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)

example

# 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

As I mentioned in some other answers , since Python 3.6 the solution to this problem is very easy thanks to object.__set_name__ which gets called with the class object that is being defined. 正如我在其他一些答案中提到的,自Python 3.6以来,由于object.__set_name__可以使用正在定义的类对象调用,因此这个问题的解决方案非常容易。

We can use it to define a decorator that has access to the class in the following way: 我们可以使用它来定义一个可以通过以下方式访问该类的装饰器:

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)

Which can then be used as a normal decorator: 然后可以将其用作普通装饰器:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM