前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于python装饰器可能是最全的一篇文章(包括有用的例子)

关于python装饰器可能是最全的一篇文章(包括有用的例子)

作者头像
阿章-python小学生
发布2018-08-03 09:52:50
3650
发布2018-08-03 09:52:50
举报
文章被收录于专栏:python全栈布道师

装饰器

1.一般语法和可能的实现

(1) 作为一个函数

这种方式最简单,就是编写一个函数,返回包装原始函数调用的一个子函数

代码语言:javascript
复制
def mydecorator(function):
    def wrapped(*args, **kwargs):
    # 函数调用之前, 做点什么
    result = function(*args, **kwargs)
    # 函数调用之后, 做点什么
    return result
return wrapped

(2) 作为一个类

如果需要复杂的参数化或者依赖于特定的状态, 那么使用类的方式更好

代码语言:javascript
复制
class DecoratorClass:
    def __init__(self, function):
        self.function = function
    def __call__(self, *args, **kwargs):
        # 在函数调用之前做点什么
        result = self.function(*args, **kwargs)
        # 在函数调用之后做点什么
        return result

(3) 参数化装饰器

有的时候需要给装饰器传递一些参数, 解决方法也很简单就是需要第二层包装.

举例给定重复次数, 每次被调用时,都会重复执行一个被装饰的函数

代码语言:javascript
复制
def repeat(number=3):
    """
    多次重复执行被装饰的函数, 返回最后一次执行的结果
    :param number: 重复次数, 默认是3
    :return:
    """
    def actual_decorator(function):
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(number):
            result = function(*args, **kwargs)
            return result
        return wrapper
    return actual_decorator

(4) 保持内省的装饰器

一个常见错误是使用装饰器时不保存函数元数据(文档字符串和函数名字), 装饰器返回的是新函数,失去了函数元数据.

这就需要functools.wraps

代码语言:javascript
复制
from functools import wraps
def preserving_decrator(function):
    @wraps(function)
    def wrapped(*args, **kwargs):
        """包装函数内部文档"""
        return function(*args, **kwargs)
    return wrapped
@preserving_decrator
def function_with_import_docstring():
    """这是想要保存的重要的函数元数据"""
print(function_with_import_docstring.__name__)
# function_with_import_docstring
print(function_with_import_docstring.__doc__)

# 这是想要保存的重要的函数元数据

2. 装饰器用法和有用的例子

(1) 参数检查

代码语言:javascript
复制
xml-rpc是一种基于HTTP使用xml进行通信的rpc协议, 但是python没有静态类型检查, 可以通过装饰器实现.
rpc_info = {}
def xmlrpc(in_=(), out=(type(None),)):
 def _xmlrpc(function):
 # 注册签名
 func_name = function.__name__
 rpc_info[func_name] = (in_, out)
 def __xmlrpc(*args):
 """包装过的函数"""
            # 检查输入的内容
 checkable_args = args[1:]  # 去掉self
 _checktypes(checkable_args, in_)
 # 运行函数
 result = function(*args)
 # 检查输出的内容
 if not type(result) in (tuple, list):
                checkable_result = (result,)
 else:
                checkable_result = result
            _checktypes(checkable_result, out)
 def _checktypes(elements, 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 not isinstance(arg, of_the_right_type):
 raise TypeError("arg #%d should be %s" % (index,                    of_the_right_type))
 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
print(rpc_info)
# {'meth1': ((<class 'int'>, <class 'int'>), (<class 'NoneType'>,)), 'meth2': ((<class 'str'>,), (<class 'int'>,))}
my = RPCView()
my.meth1(1, 2)
# received 1 and 2
my.meth2(2)
"""
Traceback (most recent call last):
  File "/Users/chenzhang/PycharmProjects/fisher/ceshi.py", line 133, in <module>
    my.meth2(2)
  File "/Users/chenzhang/PycharmProjects/fisher/ceshi.py", line 92, in __xmlrpc
    _checktypes(checkable_args, in_)
  File "/Users/chenzhang/PycharmProjects/fisher/ceshi.py", line 110, in _checktypes
    raise TypeError("arg #%d should be %s" % (index, of_the_right_type))
TypeError: arg #0 should be <class 'str'>
"""

(2) 缓存

缓存的前提是相同的输入无论如何输出都是一样的, 这种编程风格是函数式编程的思想.缓存的时候需要将函数的名字和调用参数放在一起作为键, 这种行为成为memorizing.

代码语言:javascript
复制
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 memorize(duration=10):
 def _memorize(function):
 def __memorize(*args, **kwargs):
            key = compute_key(function, args, kwargs)
 # 是否已经拥有它了?
 if (key in cache and not is_obsolete(cache[key], duration)):
 print('we got a winner')
 return cache[key]['value']
 # 计算
 result = function(*args, **kwargs)
 # 保存结果
 cache[key] = {
 'value': result,
 'time': time.time()
            }
 return result
 return __memorize
 return _memorize
@memorize()
def very_very_very_complex_stuff(a, b):
    time.sleep(3)
 return a + b
print(very_very_very_complex_stuff(2, 2))
# 4
print(very_very_very_complex_stuff(2, 2))
# we got a winner
# 4
print(cache)
# {'c4ba025dbed84bd8eb75d4beacf6900922117068': {'value': 4, 'time': 1532080242.420179}}
time.sleep(15)
print(very_very_very_complex_stuff(2, 2))
# 4
(3)  代理
代理装饰器使用全局机制来标记和注册函数. 比如根据当前用户的角色是否是admin用户来进行集中式的安全检查和权限判断.
class User:
 def __init__(self, roles):
 self.roles = roles
class Unauthorized(Exception):
 pass
def protect(role):
 def _protect(function):
 def __protect(*args, **kwargs):
            user = globals().get('user')
 if user is None or role not in user.roles:
 raise Unauthorized("I won't tell you")
 return function(*args, **kwargs)
 return __protect
 return _protect
tarek = User(('admin', 'user'))
bill = User(('user',))
class MySecrets:
 @protect('admin')
 def waffle_recipe(self):
 print('use tons of buffer!')
there_are = MySecrets()
user = tarek
there_are.waffle_recipe()
# use tons of buffer!
user = bill
there_are.waffle_recipe()
"""
Traceback (most recent call last):
  File "/Users/chenzhang/PycharmProjects/fisher/ceshi.py", line 38, in <module>
    there_are.waffle_recipe()
  File "/Users/chenzhang/PycharmProjects/fisher/ceshi.py", line 15, in __protect
    raise Unauthorized("I won't tell you")
__main__.Unauthorized: I won't tell you
"""
(4) 上下文提供者
上下文装饰器确保函数运行在正确的上下文中, 比如数据操作多线程共享的时候,用同一个锁来包装只有一个线程访问.
不过通常会用上下文管理器替代.
from threading import RLock
lock = RLock()
def synchronized(function):
 def _synchronized(*args, **kwargs):
        lock.acquire()
 try:
 return function(*args, **kwargs)
 finally:
            lock.release()
 return _synchronized
@synchronized
def thread_safe():
 print('确保锁定资源')
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 python全栈布道师 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档