关于python:如何在decorator中区分方法和函数?

How to differentiate between method and function in a decorator?

我想编写一个根据应用于函数还是方法而不同的修饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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(),但没有运气。问题是,一个方法实际上既不是绑定的也不是未绑定的方法,而是一个普通的函数,只要它是从类体内部访问的。

我真正想做的是将修饰器的操作延迟到类实际被实例化的程度,因为我需要在它们的实例范围内调用这些方法。为此,我想用一个属性标记方法,然后在调用MyClass.__new__()方法时搜索这些属性。这个修饰符应该为其工作的类需要从我控制的类继承。你可以用这个事实来解决问题。

在正常功能的情况下,无需延迟,装饰师应立即采取措施。这就是为什么我想区分这两种情况。


我将依赖于这样的约定:将成为方法的函数有一个名为self的第一个参数,而其他函数则不脆弱,但是,没有真正可靠的方法。

所以(伪代码,因为我有注释来代替您在任何情况下想做的事情…):

1
2
3
4
5
6
7
8
9
10
11
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作为第一个参数。

不幸的是,没有一种简单的方法可以检查被包装的其他函数(非未来方法)是否没有这样的第一个参数,因为在这种情况下,您无法控制环境。修饰程序可以通过函数的f_globals属性(global s dict,即module's dict)和f_name属性检查"顶级"函数(def是其模块中的顶级语句的函数),如果函数是这样一个全局函数,则以后不会将它指定为类的属性(从而成为一个fut不管怎样,如果存在名为first arg的self,则可以诊断为错误并警告(同时仍将函数视为实际函数;-)。

另一种选择是在实际功能的假设下在装饰器本身中进行装饰,但也使原始功能对象作为wrapper._f可用。然后,元类的__init__可以为它所看到的类体中的所有函数重新进行修饰,并以此方式进行标记。这种方法比我刚画的依赖于惯例的方法要可靠得多,即使有额外的检查。不过,有点像

1
2
3
4
5
6
class Foo(Bar): ... # no decorations

@decorator
def f(*a, **k): ...

Foo.f = f   #"a killer"... function becomes method!

仍然是有问题的——您可以尝试在元类中使用__setattr__截取它(但是在class语句之后对类属性的其他赋值可能会变得有问题)。

用户的代码有更多的自由去做一些奇怪的事情(而python通常会给程序员留下很多这样的自由),你的"framework-y"代码很难让事情保持在严格的控制之下,当然;-。


您是否需要在选择返回哪个包装器的地方发生魔力,或者您是否可以将魔力推迟到实际调用函数为止?您总是可以尝试使用装饰器的参数来指示它应该使用两个包装器中的哪一个,比如

1
2
3
4
5
6
7
8
9
10
11
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方法,以查找用修饰器修饰的方法,并相应地修改它们,就像亚历克斯暗示的那样。将这个元类与您的基类一起使用,并且由于将使用decorator的所有类都将从基类继承,因此它们还将获取元类类型并使用其init。


从python 3.3开始,使用pep 3155:

1
2
3
4
5
6
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

方法A类的x将有一个__qualname__A.x,而函数x将有一个__qualname__x


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
>>>

编辑

哦,快点!我完全错了。我应该更仔细地阅读亚历克斯的帖子。

1
2
3
4
5
6
7
8
>>> class B:
...   @deco
...   def f(self, x):
...     return x +5
...
>>> b = B()
>>> b.f()
function