Python 装饰器的典型使用场景(1)

Python 装饰器的典型使用场景(1)


装饰器常用于以下方面,本文先介绍前面两种:

  • 参数检查(Agrument checking)
  • 缓存(Caching)
  • 代理(Proxy)
  • 提供上下文(Context Provider)


1. 参数检查

当函数在某些特别的上下文执行时,对其接收或返回的参数进行检查是很有用的。例如,如果某个函数将通过 XML-RPC 的方式被调用时,Python 将无法像那些静态类型语言一样能直接提供函数的完整签名。该特性需要在XML-RPC 客户端请求函数签名时提供自省能力。


注释:XML-RPC 协议

XML-RPC 是一个轻量级的远程过程调用协议,该协议在 HTTP 上使用 XML 来编码它的调用。在简单的客户端-服务器交互中,常使用该协议,而不使用SOAP。不像 SOAP 会提供一个列出所有可调用函数(WSDL)的页面,XML-RPC 没有可用函数的目录。关于扩展该协议,以允许发现服务器 API 的提议已经被提出,并且 Python 的 xmlrpc 模块已经实现了该功能(参考:https://docs.python.org/3/library/xmlrpc.server.html)


一个定制的装饰器就能够提供这种类型的签名。就所定义的签名参数而言,装饰器还可以确保要输入什么,要得到什么输出。

rpc_info = {}

def xmlrpc(in_=(), out=(type(None),)):
    def _xmlrpc(function):
        # registering the signature
        func_name = function.__name__
        rpc_info[func_name] = (in_, out)

        def _check_types(elements, types):
            """Subfunction that checks the types."""
            if len(elements) != len(types):
                raise TypeError('argument count is wrong')
            typed = enumerate(zip(elements, types))
            for index, couple in typed:
                arg, of_the_right_type = couple
                if isinstance(arg, of_the_right_type):
                    continue
                raise TypeError('arg #%d should be %s' % (index, of_the_right_type))

       # wrapped function
       def __xmlrpc(*args): # no keywords allowed
           # checking what goes in
           checkable_args = args[1:] # removing self
           _check_types(checkable_args, in_)
           # running the function
           res = function(*args)
           # checking what goes out
           if not type(res) in (tuple, list):
               checkable_res = (res,)
           else:
               checkable_res = res
           _check_types(checkable_res, out)
           # the function and the type
           # checking succeeded
           return res
        return __xmlrpc
    return _xmlrpc

该装饰器把函数注册到一个全局的字典中,并且保存了函数所接受参数的类型和返回值类型的列表。这个例子高度简化只是为了用来展示一下参数检查装饰器:

下面是一个使用该装饰器的例子:

class RPCView:
    @xmlrpc((int, int)) # two int -> None
    def meth1(self, int1, int2):
        print('received %d and %d' % (int1, int2))
    
    @xmlrpc((str,), (int,)) # string -> int
    def meth2(self, phrase):
        print('received %s' % phrase)
        return 12
当这个类被读入后,该类的定义就会被存在 rpc_info 这个字典中,这些信息可以在一些需要检查参数类型的特殊环境中使用。

>>> rpc_info
{'meth2': ((,), (,)), 'meth1': ((, ), (,))}
>>> my = RPCView()
>>> my.meth1(1, 2)
received 1 and 2
>>> my.meth2(2)
Traceback (most recent call last):
    File "", line 1, in 
    File "", line 26, in __xmlrpc
    File "", line 20, in _check_types
TypeError: arg #0 should be 


2. 缓存

缓存装饰器和参数检查装饰器很像,但是更关注那些内部状态不会影响输出的函数。对于这样的函数,其每一组参数都可以关联到一个独特的结果。这种风格的编程是函数式编程的特色(函数编程请查看:http://en.wikipedia.org/wiki/Functional_programming),当输入值的集合是有限的时可以使用。

因此,缓存装饰器能够将输出结果和产生这个结果所需要的输入参数绑定起来,这样在随后的相同调用中就可以直接返回结果。这种行为叫做记忆(memoizing,http://en.wikipedia.org/wiki/Memoizing),并且使用装饰器来实现是很简单的:

import time
import hashlib
import pickle

cache = {}

def is_obsolete(entry, duration):
    return time.time() - entry['time']> duration

def compute_key(function, args, kw):
    key = pickle.dumps((function.__name__, args, kw))
    return hashlib.sha1(key).hexdigest()

def memoize(duration=10):
    def _memoize(function):
        def __memoize(*args, **kw):
            key = compute_key(function, args, kw)
            # do we have it already ?
            if (key in cache and not is_obsolete(cache[key], duration)):
                print('we got a winner')
                return cache[key]['value']
            # computing
            result = function(*args, **kw)
            # storing the result
            cache[key] = {
                'value': result,
                'time': time.time()
            }
            return result
        return __memoize
    return _memoize

上面的代码中,使用有序参数值产生的 SHA 哈希键值,和函数生成的结果被保存到了全局的字典中。哈希生成过程中使用了 pickle,这是保存所有被传作参数的对象状态的一个简单方式。假如,一个套接字会线程被用作参数的话,就会抛出 PicklingError(参考:https://docs.python.org/3/library/pickle.html)。当距离最后一次函数调用已经过了很久时,duration 这个参数会被用来使缓存的值失效。

下面是使用该装饰器的一个例子:

>>> @memoize()
... def very_very_very_complex_stuff(a, b):
...     # if your computer gets too hot on this calculation
...     # consider stopping it
...     return a + b
...
>>> very_very_very_complex_stuff(2, 2)
4
>>> very_very_very_complex_stuff(2, 2)
we got a winner
4
>>> @memoize(1) # invalidates the cache after 1 second
... def very_very_very_complex_stuff(a, b):
...     return a + b
...
>>> very_very_very_complex_stuff(2, 2)
4
>>> very_very_very_complex_stuff(2, 2)
we got a winner
4
>>> cache
{'c2727f43c6e39b3694649ee0883234cf': {'value': 4, 'time': 1199734132.7102251)}
>>> time.sleep(2)
>>> very_very_very_complex_stuff(2, 2)
4

缓存计算代价‘昂贵’的函数,能够显著地提升应用程序的性能,但是使用时要小心。缓存的值也可以被绑定到函数自身,来管理其作用域,生命周期,而不是存放到一个集中的字典中。但是,更高效的缓存装饰器应该使用以高级缓存算法为基础的特殊的缓存库。





你可能感兴趣的:(Python 装饰器的典型使用场景(1))