一位Python初学者的独白:Python小白眼中的装饰器

这是在微信看到的一篇文章,伙伴刚学Python不久,我认真看了一下,确实是很真实的刚入门Python学习的伙伴!我把这篇文章拿到这来,也希望更多刚接触Python的伙伴能有所学,有所悟!

一位Python初学者的独白:Python小白眼中的装饰器

 

原文如下:

hello,大家好,首先介绍一下,我并不是一名真正意义上的程序猿。最近在学习Python这门语言,希望有个地方可以记录爬过的坑。站在研发鄙视链最底端,也是想通过一些手段来减少重复性工作来提高效率。那Python这门语言就能很好的帮我解决这些问题。今天就带你认识一下小白眼中的“装饰器”。

小白学习,概念是必须*3要学习并理解的!!那什么是装饰器,装饰器又可以做些什么呢!?

首先概念,装饰器是闭包的一种应用(闭包后面会讲到,本次不做阐述),需要满足一下规则:

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中的“魔法”之一,函数装饰器也就差不多讲完了,其实装饰器同样可以写成类装饰器,以及更为广泛的用法,是我这个小白还没有接触到的~~希望能够得到大佬的点(怒)拨(怼)!

至于Python当中的“魔法”还有很多很多,比如说上面代码中,md5_pwd = MD5Password明明是一个对象,为什么还能够像函数(md5_pwd())一样调用,以及如何写一个类装饰器呢?这些功能都是通过Python底层的魔术__call__方法来实现的!那么下一次的主题,就是__call__方法,会讲述我对__call__方法的初识,以及重新认识!

最后,文章中肯定有讲述不对的地方,欢迎各种代码大佬来怼我,教育我,指正我!!因为我真的想学好Python!!

我的愿景是立志于做一名开发、测试一条龙的工程师!代码越写越优雅!(然后,和女朋友接私活~~~hhhh)很真实的小伙伴了啊,毕竟曾经我也有过这样的想法,哈哈哈!大家在刚接触Python的时候是怎样的心情和感悟呢!也可以留言哈!

文章原创: xiaoanzi 种豆儿得瓜

猜你喜欢

转载自www.cnblogs.com/cherry-tang/p/11131608.html