如何区分装饰器中的方法和功能?

时间:2010-03-12 20:45:32

标签: python decorator

我想编写一个装饰器,它的行为会有所不同,具体取决于它是应用于函数还是方法。

def some_decorator(func):
    if the_magic_happens_here(func): # <---- Point of interest
        print 'Yay, found a method ^_^ (unbound jet)'
    else:
        print 'Meh, just an ordinary function :/'
    return func

class MyClass(object):
    @some_decorator
    def method(self):
        pass

@some_decorator
def function():
    pass

我尝试了inspect.ismethod()inspect.ismethoddescriptor()inspect.isfunction(),但没有运气。问题是,方法实际上既不是绑定方法也不是非绑定方法,而是普通函数,只要从类体内访问它。

我真正想做的是将装饰器的动作延迟到实际实例化类的程度,因为我需要在实例范围内调用方法。为此,我希望使用属性标记方法,然后在调用.__new__() MyClass方法时搜索这些属性。这个装饰器应该工作的类需要从我控制的类继承。您可以将这一事实用于您的解决方案。

在正常功能的情况下,延迟不是必需的,装饰者应该立即采取行动。这就是我想要区分这两种情况的原因。

4 个答案:

答案 0 :(得分:6)

我将依赖于这样的约定,即将成为方法的函数具有名为self的第一个参数,而其他函数则没有。脆弱,但那时,没有真正坚实的方式。

所以(伪代码,因为我有评论代替你想要做的任何一种情况......):

import inspect
import functools

def decorator(f):
  args = inspect.getargspec(f)
  if args and args[0] == 'self':
     # looks like a (future) method...
  else:
     # looks like a "real" function
     @functools.wraps(f)
     def wrapper  # etc etc

让它变得更加可靠的一种方法,就像你说所有涉及的类都是从你控制下的类继承而来的,就是让那个类提供一个元类(当然也会被所说的类继承)来检查东西在课堂结束时。使包装函数可访问,例如通过wrapper._f = f和元类的__init__可以检查所有包装的方法确实有self作为第一个参数。

不幸的是,没有简单的方法来检查被包裹的其他函数(非未来方法)没有有这样的第一个参数,因为你不是在这种情况下控制环境。装饰器可以检查“顶级”函数(def是其模块中的顶级语句的函数),通过f_globals(全局字典,即模块的dict)和{{1函数的属性 - 如果函数是这样的全局,大概它以后不会被指定为类的属性(从而成为未来方法;-)所以f_name命名为第一个arg,如果有,可以被诊断为错误并警告(同时仍将功能视为真正的功能; - )。

一种替代方案是在真实函数的假设下在装饰器本身中进行装饰,但也使原始函数对象可用为self。然后,元类的wrapper._f可以为它看到的类体中的所有函数重新进行装饰。即使有额外的检查,这种方法也比我刚刚描绘的惯用方法更加可靠。还是,像

__init__

仍然会有问题 - 你可以尝试在你的元类中使用class Foo(Bar): ... # no decorations @decorator def f(*a, **k): ... Foo.f = f # "a killer"... function becomes method! 来拦截它(但是__setattr__语句之后其他对类属性的赋值可能会出现问题)。

用户的代码越能自由地做出时髦的东西(Python通常会给程序员留下很多这样的自由),当然,你的“框架式”代码更难以控制事物; - 当然; )。

答案 1 :(得分:1)

你需要让魔法发生在你选择哪个包装器返回的地方,或者你可以推迟魔法直到实际调用该函数? 您总是可以尝试向装饰器提供一个参数来指示它应该使用哪两个包装器,比如

def some_decorator( clams ):
   def _mydecor(func ):
       @wraps(func)
       def wrapping(*args....)
          ...
       return wrapping
   def _myclassdecor(func):
       @wraps(func)
       .....

   return _mydecor if clams else _myclassdecor

我可能建议的另一件事是创建一个元类并在元类中定义 init 方法,以查找装饰器修饰的方法并相应地修改它们,就像Alex暗示的那样。将此元类与您的基类一起使用,并且因为将使用装饰器的所有类将继承自基类,所以它们也将获得元类类型并使用其 init

答案 2 :(得分:1)

使用PEP 3155从Python 3.3开始:

def some_decorator(func):
    if func.__name__ != func.__qualname__:
        print 'Yay, found a method ^_^ (unbound jet)'
    else:
        print 'Meh, just an ordinary function :/'
    return func

x的方法A将具有__qualname__的{​​{1}},而函数A.x将具有{的x {1}}。

答案 3 :(得分:-3)

您只需检查要装饰的函数是否具有im_func属性。如果是,那么这是一种方法。如果没有那么它就是一个功能。

注意下面的代码示例会在通话时进行检测,但您也可以在装修时进行检测。只需将hasattr检查移动到外部装饰器生成器。

Python 2.6.4 (r264:75706, Dec  7 2009, 18:45:15) 
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def deco(f):
...   def _wrapper(*args, **kwargs):
...     if hasattr(f, 'im_func'):
...       print 'method'
...     else:
...       print 'function'
...   return _wrapper
... 
>>> deco(lambda x: None)()
function
>>> def f(x):
...   return x + 5
... 
>>> deco(f)()
function
>>> class A:
...   def f(self, x):
...     return x + 5
... 
>>> a = A()
>>> deco(a.f)()
method
>>> deco(A.f)()
method
>>> 

修改

哦,快点!我完全错了。我所以应该更彻底地阅读Alex的帖子。

>>> class B:
...   @deco
...   def f(self, x):
...     return x +5
... 
>>> b = B()
>>> b.f()
function