开发者

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 fooba开发者_如何学Gor( 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; 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? or is that possibility precluded in 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; 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.


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.

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


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. 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_self is called only for the class which the descriptor is defined on. This is where modifications to foobar should be made, because the method is only called once. While init_cls is called for all classes which have access to the decorated method. This is where modifications to the classes foobar can be referenced by should be made.

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜