Python装饰器实用例子

Python里我们经常能见到@开头的句法,也就是人们常说的装饰器(decorator)。装饰器是Python非常重要的一部分,能够产出更易于维护的代码。这篇文章会给大家带来装饰器的介绍以及几个实用的例子。

装饰器是啥
假设我有一个func_foo函数如下。

def func_foo():
print("运行func_foo")
如果我们每次都要在函数前后插入日志,最简单的办法是把代码改成如下。

def func_foo():
print("在装饰器前我要做这些")
print("运行func_foo")
print("装饰器后做这些")
但是如果我们想在func_bar,func_xxx等函数中一概插入同样的日志呢,一个个函数去改不仅费时,以后也难以维护,于是会想到以下办法。

def a_decorator(a_func):
def wrap_the_func():
print("在装饰器前我要做这些")
a_func()
print("装饰器后做这些")

return wrap_the_func

def func_foo():
print("运行func_foo")

def func_bar():
print("运行func_bar")

func_foo = a_decorator(func_foo) # func1
func_bar = a_decorator(func_bar) # func2
在上面这个例子里,如果我们需要改变插入日志的内容,我们只需要改变wrap_the_func里的内容即可。但是美中不足的是我们需要添加func1以及func2额外两行代码,那么有没有办法让我们能够更简洁更Pythonic地表达我们在最后这两行中所要表达的东西呢?

当然有!那不正是装饰器嘛!在Python里,上面的例子可以写成。

@a_decorator
def func_foo():
print("运行func_foo")

@a_decorator
def func_bar():
print("运行func_bar")
简单吧,@a_decorator加到func_foo的效果是和func_foo = a_decorator(func_foo)一样的,这就是装饰器的强大之处。装饰器可以在不修改函数或者类本身代码的情况下给函数或者类增加额外的功能以及改变他们的用法。

学习Python中的小伙伴,需要学习资料的话,可以到我的微信公众号:Python学习知识圈,后台回复:“01”,即可拿Python学习资料
这里有我自己整理了一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。送给正在学习python的小伙伴!这里是python学习者聚集地,欢迎初学和进阶中的小伙伴!


了解装饰器基本概念后,接下来给大家带来几个Python装饰器在实战中的几个例子。

1)权限认证
在做服务器端的时候我们通常得检测一个用户是否有权限进行某些操作,因此在某些web handlers里可能会加上额外的逻辑来检查一下用户权限,比如下面这个WebHandler里,我们可以在每次函数开头通过调用写好的PlatformAuthenticated方法来认证一下用户。

def PlatformAuthenticated(auth, auth_level):
if not auth or not check_auth(auth.username, auth.password):
raise Exception("BlahBlahBlah")

class WebHandler:
def get(self, request, response):
auth = request.authorization(ADMIN)
PlatformAuthenticated(auth, ADMIN)
do_something()
但是这样写的问题在于一旦handlers多了,亦或者是权限分成好几种级别的话,代码的维护性以及可读性都会大大下降,因为我们需要把这些额外的代码加入到每个handler的get方法里边。这时候可以把PlatformAuthenticated变成一个装饰器,再把这个装饰器添加到相应的handlers里边。比如

def PlatformAuthenticated(auth_level):
class StrictPlatformAuth:
def init(self, handler):
self.handler = handler

    def __call__(self, request, response):
        auth = request.authorization(auth_level)
        if not auth or not check_auth(auth.username, auth.password):
            raise Exception("BlahBlahBlah")
        self.handler(request, response)

return StrictPlatformAuth

class WebHandler:
@PlatformAuthenticated(ADMIN)
def get(self, request, response):
do_something()

class RobotWebHandler:
@PlatformAuthenticated(ROBOT)
def get(self, request, response):
do_something()
上面这个装饰器相当于把get这个方法变成了

get = PlatformAuthenticated(auth_level)(get)
这样一来,通过装饰器,我们每次调用get也变成了调用PlatformAuthenticated里的call (不熟悉call的朋友可以百度/google之),这样在不需要改变每个handlers代码本身的情况下就可以进行不同层级的权限认证了。

2)载入缓存
在编写应用程序的时候,我们经常会向数据库或者数据仓库发送请求读取某块数据,而由于从数据库中读取数据非常费时,我们会把读过的数据载入到redis等基于内存的数据库里。在Python里我们也可以把这一块载入缓存的逻辑写到一个装饰器里,让有需求的函数调用这个装饰器。

下面我们就写一个这样的基本款装饰器,这个装饰器会以函数的参数作为key,返回值作为value来存入缓存里。

def cached(expires_secs):
def wrapper(func):
key = f"redis_decorator:{func.module}:{func.name}"
def execute(*args):
for arg in args:
key += str(arg)
value = redis.get(key)

        if not value:
            redis.set(key, value, expires_secs)
            value = func(*args)
        return value
    return execute
return wrapper

@cached(86400)
def run_query(query_type, date):
return execute_query(query_type, date)
因为有了cached这个装饰器,每次在跑run_query之前我们都会先检查之前是否执行过同样的查询,如果执行过得话我们理论上会在redis里找到查询的返回值,这样就不需要再从数据库里读取数据了。如果没有执行过得话我们则会从数据库里把数据读取下来,然后再把数据载入到redis里。

  1. 插入日志
    在编程中把重要的信息log出来不仅能够帮助自己调试,也有利于提高代码的可维护性。假如我们要在每个Python函数里把某些通用的信息都加入到日志当中,利用装饰器可以省去很多的功夫。比如把下面这个logger装饰器加入到函数中后,每次函数执行前都会先把函数的名字给log出来。

def logger(func):
@wraps(func)
def log(*args, *kwargs):
logging.info(func.name + " 被调用了")
return func(
args, **kwargs)
return log

@logger
def bar():
pass
也可以把log保存在不同文件上

def logger(path):
def log_decorator(func):
@wraps(func)
def log(*args, *kwargs):
with open(path, 'a') as writer:
writer.write(func.name + " 被调用了")
return func(
args, **kwargs)
return log
return log_decorator

@logger("/var/log/bar.txt")
def bar():
pass
总结
Python装饰器在写自己的库的时候真的是必不可少的利器,其他常见的应用还有单元测试,安排协程序(coroutine),单例模式等等,以后有空再补上更多的例子。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,...
    chen_000阅读 1,359评论 0 3
  • 一. 有时候我们会有这样需求: 在原有的逻辑前后添加一段逻辑 如: 在增/删/改操作之前检查用户是否登录、某个操...
    元亨利贞o阅读 695评论 1 4
  • 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返...
    萧十一郎456阅读 328评论 0 0
  • 今天下午一个客户过来换点火线圈和火花塞,之前我就给他检查过该换了点火线圈一个坏了,上次没时间换今天抽空来换了,直接...
    AAAAA京心达张水尚阅读 165评论 0 0
  • 酸豆角炒鸡杂 莲藕条炒蹄筋
    此木的优悠阅读 221评论 0 0