2013-10-11 18 views
3

目标是我不想有一个装饰器可以同时使用函数和实例方法,并且当装饰器应用于方法或函数对象本身时,我想在包装函数中检索self对象适用于功能。如何检测是装饰器已应用于方法或功能?

这里是我发现差不多的工作,这仅仅是我使用来检测FUNC什么已应用于装饰:

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 

这确实对我的工作,有一个小小的例外,它抛出当我在多个进程中并行运行测试时发生异常。

回答

0

由于这个苏答案:Using the same decorator (with arguments) with functions and methods

我来到这个解决方案女巫完美的作品对我来说:

def proofOfConcept(): 
    def wrapper(func): 

     class MethodDecoratorAdapter(object): 
      def __init__(self, func): 
       self.func = func 
       self.is_method = False 

      def __get__(self, instance, owner): 
       if not self.is_method: 
        self.is_method = True 
       self.instance = instance 

       return self 

      def __call__(self, *args, **kwargs): 
       # Decorator real logic goes here 
       if self.is_method: 
        return self.func(self.instance, *args, **kwargs) 
       else: 
        return self.func(*args, **kwargs) 

     return wraps(func)(MethodDecoratorAdapter(func)) 

    return wrapper 

注意这不是线程安全的,有一个线程安全的方法一绝返回__get__的可召回对象,其范围与实例相关

2

您可以使用inspect.getargspec

import inspect 

def _is_method(func): 
    spec = inspect.getargspec(func) 
    return spec.args and spec.args[0] == 'self' 

用法示例:

>>> def dummy_deco(f): 
...  print('{} is method? {}'.format(f.__name__, _is_method(f))) 
...  return f 
... 
>>> @dummy_deco 
... def add(a, b): 
...  return a + b 
... 
add is method? False 
>>> class A: 
...  @dummy_deco 
...  def meth(self, a, b): 
...   return a + b 
... 
meth is method? True 

注意此代码依赖于第一个参数的名称。如果该名称不是self,它会将其视为非实例方法,即使它是。

+0

谢谢,看起来像这样可以解决我的问题,但我想使用'inspect'根本不是“优雅”的方法,也许还有其他方法呢? – canni

1

您可以solve this problem using descriptor protocol。通过从装饰器返回非数据描述符,您可以实现__get__,您可以在其中保存方法的实例/类。

另一个(更简单)的方法是在装饰器制作的包装器中检测实例/类,可能有selfcls作为*args的第一个。这提高了修饰函数的“可检查性”,因为它仍然是一个普通函数,而不是一个自定义的非数据描述符/函数对象。

问题我们要解决的是,我们不能钩住或之前method binding

注意,从功能对象(未结合的或结合的)的转变 方法对象发生每个属性从检索时间类 或实例。

换句话说:当封装器运行时,它的描述符协议,功能即__get__方法的包装器,已绑定类/实例的功能和已经在执行产生的方法。我们留下了args/kwargs,并且在当前堆栈框架中没有直接可访问的与类相关的信息。

让我们先从解决类/ STATICMETHOD特殊情况和实施包装为简单的打印机:

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 

这里谈到棘手的部分 - 如果这是一个方法/类方法调用,第一个参数的个数必须是实例/类分别。如果是这样,我们可以从这个arg中得到我们执行的方法。如果是这样,我们上面实现的包装将在内部为__func__。如果是这样,original成员将在我们的包装。如果它与闭包中的fun相同,我们就回家了,可以从其余的参数中安全地分割实例/类。

def _declassify(fun, args): 
    if len(args): 
     met = getattr(args[0], fun.__name__, None) 
     if met: 
      wrap = getattr(met, '__func__', None) 
      if wrap.original is fun: 
       maybe_cls = args[0] 
       cls = maybe_cls if isclass(maybe_cls) else maybe_cls.__class__ 
       return cls, args[1:] 
    return None, args 

让我们来看看这是否与函数/方法不同的变体:

@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 

让我们来看看这个实际运行:

simplefun() 
instance = Class() 
instance.method(1, 2) 
instance.staticmethod(a1=3) 
instance.classmethod() 
Class.staticmethod(a1=3) 
Class.classmethod() 

输出:

$ 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: {}   
0

解决方案python3:

import inspect 

def _is_method(func): 
    spec = inspect.signature(func) 
    if len(spec.parameters) > 0: 
     if list(spec.parameters.keys())[0] == 'self': 
      return True 
    return False 
相关问题