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

装饰器

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

(1) 作为一个函数

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

def mydecorator(function):
    def wrapped(*args, **kwargs):
    # 函数调用之前, 做点什么
    result = function(*args, **kwargs)
    # 函数调用之后, 做点什么
    return result
return wrapped

(2) 作为一个类

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

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

(3) 参数化装饰器

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

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

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

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) 参数检查

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.

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('确保锁定资源')

原文发布于微信公众号 - python全栈布道师(gh_f7cbe2f9567b)

原文发表时间:2018-07-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Google protocol buffer简介

Google Protocol Buffer Google Protocol Buffer又简称Protobuf,它是一种很高效的结构化数据存储格式,一般用于结...

35760
来自专栏java学习

Java每日一练(2017/6/29)

Java基础 | 数据库 | Android | 学习视频 | 学习资料下载 最新通知 ●回复"每日一练"获取以前的题目! ●【新】Ajax知识点视频更新了!(...

380120
来自专栏微信公众号:Java团长

静态代理 VS 动态代理

1.通过DRP这个项目,了解到了动态代理,认识到我们之前一直使用的都是静态代理,那么动态代理又有什么好处呢?它们二者的区别是什么呢?

10430
来自专栏技术记录

通讯协议序列化解读(一) Protobuf详解教程

59170
来自专栏锦小年的博客

python学习笔记3.4-函数装饰器

软件开发的过程中,最基本的技能就是:不要重复自己的工作。也就是说,在任何时候,当需要创建高度重复的代码时,通常都需要寻找一个更加快捷的解决方案。在python中...

25460
来自专栏DOTNET

【翻译】MongoDB指南/引言

【原文地址】https://docs.mongodb.com/manual/ 引言 MongoDB是一种开源文档型数据库,它具有高性能,高可用性,自动扩展性 1...

28660
来自专栏Android 研究

Retrofit解析5之代理设计模式

即Proxy Pattern,23种常用的面向对象软件设计模式之一。(设计模式的说法源自<设计模式>一书,原名<Design Patterns:Elements...

15030
来自专栏不止是前端

深入Node

375130
来自专栏Python自动化测试

装饰器的简单应用

在Python的函数中,函数的参数我们成为形式参数,想比较而言,默认参数在实际的应用中更加丰富,还有一种情况就是函数的参数是函数,特别是在接口自动...

10420
来自专栏H2Cloud

FFLIB之FFLUA——C++嵌入Lua&扩展Lua利器

摘要: 在使用C++做服务器开发中,经常会使用到脚本技术,Lua是最优秀的嵌入式脚本之一。Lua的轻量、小巧、概念之简单,都使他变得越来越受欢迎。本人也使用过p...

75870

扫码关注云+社区

领取腾讯云代金券