如何在装饰器中区分方法和函数?
我想写一个装饰器,它的行为会根据是应用在函数上还是方法上而有所不同。
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__()
方法被调用时,去查找这些属性。这个装饰器需要作用的类必须继承自一个我可以控制的类。你可以利用这一点来解决问题。
对于普通函数来说,延迟是没有必要的,装饰器应该立即生效。这就是我想区分这两种情况的原因。
4 个回答
你是否需要在选择返回哪个包装器时让事情变得神奇,还是可以等到函数真正被调用时再让它变得神奇?
你可以尝试给你的装饰器加一个参数,来指明应该使用哪一个包装器,比如:
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方法。
从Python 3.3开始,使用了一个叫做PEP 3155的规范:
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
我会依赖一个约定:那些会变成方法的函数,第一个参数叫做 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
。
不过,不幸的是,没有简单的方法来检查其他函数(非未来方法)在被包装时是否没有这样的第一个参数,因为在这种情况下你无法控制环境。装饰器可以通过 f_globals
(全局字典,也就是模块的字典)和 f_name
属性来检查“顶层”函数(那些在模块中直接定义的函数)——如果这个函数是全局的,推测它不会被后续赋值为类的属性(这样就会变成未来方法;-),所以如果有 self
作为第一个参数,就可以判断是错误的并发出警告(同时仍然把这个函数当作真正的函数;-)。
另一种选择是在装饰器内部进行装饰,假设这是一个真正的函数,同时也将原始函数对象作为 wrapper._f
提供。然后,元类的 __init__
可以重新对它看到的所有被标记的类体中的函数进行装饰。这种方法比我刚才提到的依赖约定的方法要可靠得多,即使有额外的检查。尽管如此,像
class Foo(Bar): ... # no decorations
@decorator
def f(*a, **k): ...
Foo.f = f # "a killer"... function becomes method!
这样的情况仍然会有问题——你可以尝试在元类中用 __setattr__
来拦截这个,但在 class
语句之后对类属性的其他赋值可能会变得麻烦。
用户的代码越自由(而且 Python 通常给程序员很多这样的自由),你的“框架式”代码就越难以保持严格控制,这当然是个挑战;-)。