带有函数注解的装饰器

4 投票
2 回答
1050 浏览
提问于 2025-04-17 16:59

在PEP 3107和这个StackOverflow的回答中提到,Python3K的函数注解和装饰器是非常配合的——也就是说,我应该能够写一个装饰器,能够和函数的属性一起使用。

不过,我现在还搞不清楚怎么才能让它们像我想的那样工作。

考虑一下这个例子:

def verbose(lcls):
    def wrap(f):
        print('inside wrap')
        def wf(*args):
            print('inside wf')
            print('lcls in wf:',lcls)
            print('locals in wf:',locals())
            print('wf annotations:',wf.__annotations__)
            print('xYx annotations:',xXy.__annotations__)
            r=f(*args)
            print('after f(*args)')
            return r
        return wf
    return wrap           

@verbose(locals())    
def xXy(x: 'x in x\'s', y: 'y in Y\'s') -> ('x times y','in x and y units'):
    print('locals in xXy:',locals())
    return x*y

xy=xXy(10,3)    
print(xy)

打印结果是:

inside wrap
inside wf
lcls in wf: {'xXy': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>, '__doc__': None, 'verbose': <function verbose at 0x109767050>, '__cached__': None, '__builtins__': <module 'builtins'>, '__package__': None, '__file__': '/private/var/folders/gx/gqtmx9mn7b75pk1gfy0m9w3w0000gp/T/Cleanup At Startup/test-383453350.857.txt', '__loader__': <_frozen_importlib.SourceFileLoader object at 0x10959ac10>, '__name__': '__main__'} 
locals in wf: {'f': <function xXy at 0x109767e60>, 'args': (10, 3), 'lcls': {'xXy': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>, '__doc__': None, 'verbose': <function verbose at 0x109767050>, '__cached__': None, '__builtins__': <module 'builtins'>, '__package__': None, '__file__': '/private/var/folders/gx/gqtmx9mn7b75pk1gfy0m9w3w0000gp/T/Cleanup At Startup/test-383453350.857.txt', '__loader__': <_frozen_importlib.SourceFileLoader object at 0x10959ac10>, '__name__': '__main__'}, 'wf': <function verbose.<locals>.wrap.<locals>.wf at 0x109767ef0>}
wf annotations: {}
xYx annotations: {}
locals in xXy: {'y': 3, 'x': 10}
after f(*args)
30

这一组代码让我看到,我无法在装饰器或者函数的属性中访问到x和y的值,这些值是在xXy这个函数中定义的。

我想要做的是:1) 有一个带有注解的函数,按照PEP 3107的规定,2) 能够有一个装饰器,可以访问这个函数的注解和调用这个函数时传入的值,而不是仅仅复制xXy的函数签名。

2 个回答

4

我想你是在找 functools.wraps() 这个东西:

def verbose(lcls):
    def wrap(f):
        print('inside wrap')
        @functools.wraps(f)
        def wf(*args):
            print('inside wf')
            print('lcls in wf:',lcls)
            print('locals in wf:',locals())
            print('wf annotations:',wf.__annotations__)
            print('xYx annotations:',xXy.__annotations__)
            r=f(*args)
            print('after f(*args)')
            return r
        return wf
    return wrap       

这是一个简单的装饰器,它可以确保一个包装函数拥有它所包装的那个函数的属性。

4

在3.3版本中,inspect.signature()这个功能可以让你在函数装饰器中获取你想要的信息。下面是一个例子,展示了如何使用它来打印每次调用被装饰函数时传入的参数名称和对应的值,同时还可以访问相关的注解:

import functools
import inspect

def verbose(wrapped):
    @functools.wraps(wrapped)  # optional - make wrapper look like wrapped
    def wrapper(*args):
        print('inside wrapper:')
        fsig = inspect.signature(wrapped)
        parameters = ', '.join('{}={}'.format(*pair)
                               for pair in zip(fsig.parameters, args))
        print('  wrapped call to {}({})'.format(wrapped.__name__, parameters))
        for parameter in fsig.parameters.values():
            print("  {} param's annotation: {!r}".format(parameter.name,
                                                         parameter.annotation))
        result = wrapped(*args)
        print('  returning {!r} with annotation: {!r}'.format(result,
                                                         fsig.return_annotation))
        return result
    return wrapper

@verbose
def xXy(x: 'x in X\'s', y: 'y in Y\'s') -> ('x times y','in X and Y units'):
    return x*y

xy = xXy(10, 3)
print('xXy(10, 3) -> {!r}'.format(xy))

输出结果:

inside wrapper:
  wrapped call to xXy(x=10, y=3)
  x param's annotation: "x in X's"
  y param's annotation: "y in Y's"
  returning 30 with annotation: ('x times y', 'in X and Y units')
xXy(10, 3) -> 30

撰写回答