How to find the class of a related method when building a class in Python 3.1?

I want to write a decorator that allows class methods to become visible to other parties; the problem I am describing, however, is not dependent on this detail. the code will look something 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 is that at the very moment when the decorator, foobar() , sees the method, it is not yet amenable to call; instead, he can see an unrelated version. perhaps this can be resolved using another decorator in the class that takes care of what needs to be done for the associated method. the next thing I’ll try to do is simply select the decorated method with the attribute when it passes through the decorator, and then use the class decorator or metaclass to do the post-processing. if I get it for work, then I don’t need to solve this riddle that still puzzles me:

can anyone in the above code fill in meaningful lines under CLASS_WHERE_METHOD_IS_DEFINED so that the decorator can actually print the class where f defined, the moment it is defined? or is this feature ruled out in python 3?

+5
python
Feb 02 2018-10-02T00
source share
3 answers

When a decorator is called, it calls the function as its argument, not a method, so it will not help the decorator to study and introspect its method as much as it wants, because it is only a function and does not carry any information about the surrounding class. Hope this solves your riddle, albeit in a negative sense!

You can try other approaches, such as deep introspection on nested stack frames, but they are hacky, fragile, and, of course, cannot be ported to other Python 3 implementations, such as pynie; Therefore, I heartily recommend avoiding them, in favor of the decorator class solution that you are already considering, and much cleaner and more durable.

+7
Feb 02 '10 at 23:17
source share

This is a very old post, but introspection is not a way to solve this problem, because it is easier to solve with metaclass and a little clever logic for constructing classes 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 doing this work is the metaclass - it searches for the attributes defined in the classes that it creates to find foobar descriptors. As soon as this happens, he passes them information about the classes in which they participate, using the init_self and init_cls descriptor methods.

init_self is called only for the class on which the handle is defined. Changes must be made to foobar , because the method is called only once. For now, init_cls is called for all classes that have access to the decorated method. References to foobar classes can be made here.

 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
May 26 '16 at 3:53
source share

As I mentioned in some other answers starting with Python 3.6, solving this problem is very easy thanks to object.__set_name__ which object.__set_name__ along with the class object being defined.

We can use it to define a decorator that has access to the class as follows:

 class class_decorator: def __init__(self, fn): self.fn = fn def __set_name__(self, owner, name): # do something with "owner" (ie 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 regular 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)> 
0
Jan 22 '19 at 21:57
source share



All Articles