使用可调用对象的装饰器时返回用户定义的函数名
考虑以下这段代码。
def print_timing(func):
import time
def wrapper(*args, **kwargs):
t1 = time.time()
res = func(*args, **kwargs)
t2 = time.time()
print '%s took %0.3f s ~ %0.0f min and %0.1f sec' % (func.func_name, t2-t1, int(t2 - t1)/60, (t2-t1) % 60 )
return res
return wrapper
@print_timing |
def foo(): |
return 'foo'
class name(object):
def __init__(self, name):
self.name = name
@print_timing
def __call__(self):
return self.name
bar = name("bar")
print bar()
这段代码的返回结果是:
__call__ took 0.000 s ~ 0 min and 0.0 sec
bar
对象 bar
的表现就像一个叫 bar
的函数,但当它和装饰器 print_timing
一起使用时,会暴露出内部实现的细节 __call__
。有没有办法改变 name
对象(也许可以通过给 __init__
函数传递合适的参数)让它返回
bar took 0.000 s ~ 0 min and 0.0 sec
?我想要一个解决方案,这样 print_timing
装饰器仍然可以和普通函数一起使用。运行 print foo()
会得到
foo took 0.000 s ~ 0 min and 0.0 sec
foo
3 个回答
1
不行。装饰器是在类创建的时候运行的,而__init__()
这个方法是在实例创建的时候运行的。你需要让装饰器把函数变成一个描述符,然后让这个描述符从实例中获取名称。
1
把 @print_timing
用作类的装饰器:
@print_timing
class name(object):
...
不需要做任何更改;你的被包装的对象现在变成了一个函数,而原本应该是一个类。不过从你的问题来看(还有它是一个可调用的类),这其实并不重要(如果重要的话,你可以修改装饰器,让返回的被包装对象看起来更好看)。
1
只要你把这个装饰器用在方法上,它们会把 self
作为第一个参数传给你:
def print_timing(func):
import time
def wrapper(*args, **kwargs):
t1 = time.time()
res = func(*args, **kwargs)
t2 = time.time()
funcname = func.__name__
# Special case; a "name" instance has a "name" attribute we want to use instead.
if len(args) >= 1 and isinstance(args[0], name):
funcname = args[0].name
print '%s took %0.3f s ~ %0.0f min and %0.1f sec' % (funcname, t2-t1, int(t2 - t1)/60, (t2-t1) % 60 )
return res
return wrapper
更新: 现在这个包装器默认使用 func.__name__
,但是如果你在一个 name
类上使用它(就像你最开始问的那样),它会用实例的 name
属性来代替。
我用 isinstance
来检查 name
属性是否存在,但你也可以用鸭子类型来判断(if hasattr(args[0], 'name')
);不过 name
这个变量太常见了,所以在随便的类方法上使用时,可能会得到意想不到的结果。