Decorators on bound methods with access to class a

2019-02-24 12:34发布

问题:

When I decorated the bound method in Python class, I need get some info in this decorator from outer class. Is that possible?

For example:

def modifier(func):
    import sys
    cls_namespace = sys._getframe(1).f_locals
    cls_namespace['data']  # dictonary has no key 'data'
    ...
    return func

class Parent:
    data = "Hi!"

class Child(Parent):

    @modifier
    def method(self):
        pass

the cls_namespace is just incomplete namespace of current class without data field that I need to get.

Is there ways to get it in decorator?

回答1:

Function decoration takes place when the class body is being executed, nothing is known about the class itself or its base classes at this time. This means that modifier decorates the unbound function object and only when func is actually called on an instance will it be bound.

You could return a wrapper function to replace the decorated function, it'll be bound instead and you'll have access to self:

from functools import wraps

def modifier(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        # self is an instance of the class
        self.data
        return func(self, *args, **kwargs)
    return wrapper

The wrapper will then be called each time method is called on an instance.

If you must have access to the base classes at class creation time, you'll have to wait until the class Child statement has completed execution. Until Python 3.6, that's only possible from a class decorator or metaclass; each is called after the class body is created and you'll have access to (at the least) the base classes.

Using a class decorator, for example:

def modifier(cls):
    # cls is the newly created class object, including class attributes
    cls.data
    return cls

@modifier
class Child(Parent):
    def method(self):
        pass

Note the location of the decorator now.

Python 3.6 adds an __init_subclass__ method that could also give you that access; the (class) method is called each time a subclass is created of the current class:

class Parent:
    def __init_subclass__(cls, **kwargs):
        # cls is a subclass of Parent
        super().__init_subclass__(**kwargs)
        cls.data

    data = "Hi!"

class Child(Parent):
    def method(self):
        pass


回答2:

This is a particularly even solution:

def wrap_class(cls):
    """Wrap a class to allow binding all decorated methods."""
    for func in cls.__dict__.values():
        if hasattr(func, '__wrapped__'):
            func.__wrapped__.im_class = cls

    return cls

Requires you to use @functools.wraps(func) and then to decorate the class with @wraps_class.

Decorator example:

def decorator(func):
    @wraps(func)
    def inner(self, *args, **kwargs):
        cls = self.__class__  # bound and straight-forward
        ...

    def extra(*args, **kwargs):
        cls = func.im_class  # Not bound
        ...

    inner.extra = extra

    return inner

Usage example:

@wrap_class
class Badger:
    @decorator
    def stoat(self, mushroom, snake):
        pass

Badger().stoat()
Badger.stoat.extra()


回答3:

Not that the very first arg passed to your decorator's wrapper will be an objects instance (self). Thanks to it you can access w=everything you need.

See Raphaels response: Related issue

def wrapped(self, *f_args, **f_kwargs):

self is the very first argument