¿Cómo puedo comprobar si el decorador se ha aplicado a un método o función?

SOURCE 5424 palabras python
El objetivo de

Descripción de los antecedentes

es que no quiero que un decorador maneje tanto la función como el método de instancia. Quiero recuperar el objeto self en la función wrapper cuando el decorador se aplica al método, o el propio objeto de función cuando se aplica a la función.
Aquí están algunas de las características que he encontrado casi válidas, y esto es s ólo lo que he utilizado para detectar qué decorador se aplica:
def _is_method(func):
    for stack_frame in inspect.stack():
        # if the code_context of the stack frame starts with 'class' this
        # function is defined within a class and so a method.
        if inspect.getframeinfo(stack_frame[0]).code_context[0].strip().startswith('class'):
            return True
    return False
Esto funciona para mí, pero con una pequeña excepción, lanza una excepción cuando ejecuto pruebas en varios procesos en paralelo.

Tal vez la mejor solución

puede seleccionar solve this problem using descriptor protocol. __get__ se puede implementar devolviendo un descriptor no - datos del decorador, donde se puede guardar una instancia/clase de método.
Otro método (más simple) es la detección tardía de instancias/clases en un envoltorio hecho por el decorador, que puede tomar self o cls como el primero de *args. Esto aumenta la "comprobabilidad"de las funciones modificadoras, ya que sigue siendo una función normal en lugar de un objeto personalizado no - descriptor/función de datos.
El problema que tenemos que resolver es que no podemos conectarnos a method binding o antes:

Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance.


En otras palabras: cuando nuestro envoltorio se está ejecutando, su Protocolo descriptor, el envoltorio de método de la función __get__, ha vinculado la función a la clase/instancia, y el método resultante se ha ejecutado. Sólo nos quedan args/kwargs, y no hay información directamente accesible sobre la clase en el marco de pila actual.
Empecemos resolviendo el caso especial class/static Method e implementando el envoltorio como una impresora simple:
def decorated(fun):
    desc = next((desc for desc in (staticmethod, classmethod)
                 if isinstance(fun, desc)), None)
    if desc:
        fun = fun.__func__

    @wraps(fun)
    def wrap(*args, **kwargs):
        cls, nonselfargs = _declassify(fun, args)
        clsname = cls.__name__ if cls else None
        print('class: %-10s func: %-15s args: %-10s kwargs: %-10s' %
              (clsname, fun.__name__, nonselfargs, kwargs))

    wrap.original = fun

    if desc:
        wrap = desc(wrap)
    return wrap
Aquí está la parte difícil - si se trata de una invocación de método/clase, el primer argumento debe ser instancia/clase, respectivamente. Si es así, podemos obtener el método que ejecutamos de este parámetro. Si es así, el envoltorio que Implementamos arriba aparecerá internamente como __func__. Si es así, el miembro original aparecerá en nuestro paquete. Si es lo mismo que fun en el cierre, entonces estamos en casa y podemos cortar la instancia/clase de forma segura a partir de los parámetros restantes.
def _declassify(fun, args):
    if len(args):
        met = getattr(args[0], fun.__name__, None)
        if met:
            wrap = getattr(met, '__func__', None)
            if getattr(wrap, 'original', None) is fun:
                maybe_cls = args[0]
                cls = maybe_cls if isclass(maybe_cls) else maybe_cls.__class__
                return cls, args[1:]
    return None, args
Veamos si esto se aplica a diferentes variantes de función/método:
@decorated
def simplefun():
    pass

class Class(object):
    @decorated
    def __init__(self):
        pass

    @decorated
    def method(self, a, b):
        pass

    @decorated
    @staticmethod
    def staticmethod(a1, a2=None):
        pass

    @decorated
    @classmethod
    def classmethod(cls):
        pass
Veamos si realmente funciona.
simplefun()
instance = Class()
instance.method(1, 2)
instance.staticmethod(a1=3)
instance.classmethod()
Class.staticmethod(a1=3)
Class.classmethod()
Salida:
$ python Example5.py 
class: None       func: simplefun       args: ()         kwargs: {}        
class: Class      func: __init__        args: ()         kwargs: {}        
class: Class      func: method          args: (1, 2)     kwargs: {}        
class: None       func: staticmethod    args: ()         kwargs: {'a1': 3} 
class: Class      func: classmethod     args: ()         kwargs: {}        
class: None       func: staticmethod    args: ()         kwargs: {'a1': 3} 
class: Class      func: classmethod     args: ()         kwargs: {}