使用可调用对象的装饰器时返回用户定义的函数名

2 投票
3 回答
669 浏览
提问于 2025-04-16 18:32

考虑以下这段代码。

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 这个变量太常见了,所以在随便的类方法上使用时,可能会得到意想不到的结果。

撰写回答