温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么用Python编写一个装饰器

发布时间:2021-11-20 16:46:01 来源:亿速云 阅读:252 作者:iii 栏目:编程语言

本篇内容主要讲解“怎么用Python编写一个装饰器”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么用Python编写一个装饰器”吧!

首先概念,装饰器是闭包的一种应用,需要满足一下规则:

1.在不更改原功能函数的内部代码,并且改变调用方法的情况下为原函数增加新功能

2.遵循开放封闭原则,什么是开放封闭原则呢?

a.已实现的功能可以添加或扩展新的功能(开放原则)

b.不修改已实现功能的内部代码(封闭原则)

其次作用,登录验证、函数运行时长统计、执行函数之前做的准备工作,执行函数之后清理功能,总之你能想到的扩展功能大部分都可以实现,而且是在原功能代码不做修改的情况下就可以优雅的完成!

一起来看下装饰器的实践,亲身经历的问题!因为工作当中经常性需要获取接口数据,所以就简单的封装了一个获取数据的方法,代码如下:

import requests
import re
def send_request_by(method, url, data):
 """
 请求接口获取数据
 :param method: 发起请求的方式
 :param url: 请求地址
 :param data: 请求数据
 :return:
 """
 if re.match("POST", method, flags=re.IGNORECASE):
 response = requests.post(url, data=data)
 if re.match("GET", method, flags=re.IGNORECASE):
 response = requests.get(url, data=data)
 return response

目前看来对自己的需求已经满足,但是每次请求的时候发现还是报错!最终通过抓包工具分析发现,在客户端进行接口调用的时都多了一个"sign"字段,这个字段是怎么来的呢?经过分析"sign"是在加密后得来的,为了解决这个问题对代码进行了一次改造,代码如下:

def send_request_by(method, url, data):
 md5_pwd = MD5Password()
 data['sign'] = md5_pwd(data)
 if re.match("POST", method, flags=re.IGNORECASE):
 response = requests.post(url, data=data)
 if re.match("GET", method, flags=re.IGNORECASE):
 response = requests.get(url, data=data)
 return response

这样看起来是解决了问题,但其实没有灵活的解决问题。试问,如果哪天又不需要加密签名是不是还得把新加的两行代码干掉?那怎么能不修改原功能又能添加加密的功能呢。经过和大佬讨教之后,发现可以通过装饰器来实现,再一次对代码进行了改造,代码如下:

def sign_md5(func):
 def wrapper(*args, **kwargs):
 if not kwargs.get('data'):
 raise KeyError("not found Key 'data'")
 if kwargs.get('data') is None:
 raise ValueError(f'{kwargs.get("data")} of value is None')
 # 首字母排序
 sort_data = json.loads(json.dumps(kwargs.get('data'), sort_keys=True))
 # 私有加密规则,生成签名
 md5_pwd = MD5Password()
 sign = md5_pwd(sort_data)
 sort_data['sig'] = sign
 kwargs['data'] = sort_data
 ret = func(*args, **kwargs)
 return ret
 return wrapper
 @sign_md5 # send_request_by = sign_md5(send_request_by)
 def send_request_by(method, url, data):
 # md5_pwd = MD5Password()
 # data['sign'] = md5_pwd(data)
 if re.match("POST", method, flags=re.IGNORECASE):
 response = requests.post(url, data=data)
 if re.match("GET", method, flags=re.IGNORECASE):
 response = requests.get(url, data=data)
 return response

经过测试之后发现,非常灵活的解决问题,不需要加密的时候注释掉@sign_md5即可!那么这个@sign_md5到底做了什么?

其实质就是在没有使用"@"魔法的情况下是sign_md5(send_request_by)(method, ulr, data),而当使用"@"魔法进行装饰后,代码执行到此行时解析器会将被装饰的send_request_by作为一个参数传递给sign_md5,即send_request_by这个函数已经作为变量传递给了sign_md(func)中的func参数,并返回了wrapper这个函数,在接下来调用send_request_by(method, url, data)这个函数,其实此时send_request_by已经不是原先的def send_request_by(method, url, data)中的send_request_by,而是指向了wrapper(*args, **kwargs)这个函数。wrapper这个函数接收任意参数,所以当执行send_request_by(method, url, data)函数时,其实把method, url, data参数传递给warpper函数,并执行wrapper内部代码,而wrapper函数内部的func就是我们传入的send_request_by函数了,意思可以理解为,我们将参数传给了wrapper函数,在wrapper函数内将请求参数进行加密后,传递给了send_request_by函数进行请求,从而在请求之前完成了加密的过程。而wrapper函数内func(*args, **kwargs)指向的是send_request_by(method, url, data)而ret也就是send_request_by(method, url, data)函数的返回结果,因此将ret返回出来。这块比较绕口,多捋一捋相信凭你的聪明才智一定会明白!到时你一定会认为,哇!竟然如此简单!!!而到此为止,最简单的无参数装饰器就此完成!领导一定会夸你,秀儿!

那么,在解决了这个需求之后自己又有了新的疑惑,如果哪天开发不按照字段首字母排序了,我该怎么办!??是不是又要重新写装饰器了???那有什么办法能在装饰器内控制是否排序呢?那么也是经过各种脑补,又又又进行了一次代码的修改,代码如下:

def sign_sort(sort=True):
 def sign_md5(func):
 def wrapper(*args, **kwargs):
 if not kwargs.get('data'):
 raise KeyError("not found Key 'data'")
 if kwargs.get('data') is None:
 raise ValueError(f'{kwargs.get("data")} of value is None')
 # sort 参数控制是否按首字母排序
 sort_data = json.dumps(kwargs.get('data'), sort_keys=True) if sort else json.dumps(kwargs.get('data'))
 sort_data = json.loads(sort_data)
 # 私有加密规则,生成签名
 md5_pwd = MD5Password()
 sign = md5_pwd(sort_data)
 sort_data['sig'] = sign
 kwargs['data'] = sort_data
 ret = func(*args, **kwargs)
 return ret
 return wrapper
 return sign_md5
@sign_sort(sort=True) # send_request_by = sign_sort(sort=Ture)(send_request_by)
def send_request_by(method, url, data):
 print(data)
 if re.match("POST", method, flags=re.IGNORECASE):
 response = requests.post(url, data=data)
 elif re.match("GET", method, flags=re.IGNORECASE):
 response = requests.get(url, data=data)
 return respons

那这一次是做了哪些优化呢?其实还是在学习无参装饰器的基础之上学习了一下带参数装饰器。仍然还是不变的。我们一起来分析一下,

首先来看sign_sort(sort=True),sign_sort实质是一个函数接收一个参数,并返回sign_md5函数。

当代码执行到"@"所在行时,同样会把被装饰send_request_by函数作为一个参数传递给sign_sort(sort=True)函数的调用结果(即sign_md5)。

也就是说send_request_by函数又是作为一个变量(函数也可以是变量)传递给了sign_md5函数中的func参数,并返回了一个wrapper函数。

在后面调用send_request_by(method, url, data)函数时,同样此时的send_request_by已经不再是def send_request_by(method, url, data)函数中的send_request_by,而是wrapper(*args, **kwargs)函数。

wrapper函数接收任意参数,所以当执行send_request_by(method, url, data)时,会把method, url, data传递给wrapper函数,执行wrapper函数内的代码,而wrapper内部的func(*args, **kwargs)还是指向send_request_by,可以理解为通过wrapper函数将参数传递给send_request_by函数,而在传递给send_request_by之前,我们可以对参数做任意的操作,因此我在传递之前判断了sort参数是否为True作为排序的开关,再对请求数据data进行加密的操作,最后带着加密签名传递给send_request_by函数进行发送请求。

到此,相信大家对“怎么用Python编写一个装饰器”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI