刚转行1年测试新手:学习Python编程经验实战分享
一、开头说两句
作为一名零基础转行刚一年的测试新手来说,深知自己在技术经验方面落后太多,难免会有急于求成的心态,这也就导致自己在学习新知识时似懂非懂,刚开始学完那会还胸有成竹,一段时间之后却又忘的一干二净,导致我要不停回去复习,还始终不得要领,难以在实践中灵活运用。
相信有不少同学跟我一样徘徊踌躇,现在老师给予了我一个给大家分享经验的机会,我也刚好结合前段时间复习关于Python装饰器的理解来说下,若有不对的地方,还望各位同学,同行,老师及时指出。
二、装饰器必知基础
其实很多知识点没有牢牢掌握,是因为最最基础的知识没有理解透彻导致。这也是我在学习装饰器时对于自己的评价,所以先让我们来聊聊学习装饰器所需要的基础知识。
1、形参与实参
函数的参数分为形式参数和实际参数,简称形参和实参。
- 形参即在定义函数时,括号内声明的参数。形参本质就是一个变量,用来接收外部传来的值
- 实参即在调用函数时,括号内传入的值,值可以是常量,变量,表达式或三者的组合
具体使用时又分为位置参数,关键字参数和默认参数
def info(name,age,sex='male')
print(f'name:{name} age:{age} sex:{sex}')
info(name='jack',18)
上述示例中,调用函数时以key=value形式的就是关键字参数,定义函数时name,age为位置参数,sex为默认参数。
注意:
- 调用函数时,实参可以是按位置或关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
- 默认参数的值通常应设为不可变类型
2、可变长度参数*args和**kwargs
参数的长度可变指的是调用函数时,实参的个数可以不固定,而在调用阶段,实参无非是按照位置或者按关键字两种形式,因此就出现了两种解决方案来处理。
2.1 可变长度的位置参数
如果在最后一个形参名前加*号,那么在调用函数时,溢出的位置实参都会被接受,以元组的形式保存下来赋值给该形参。
def func(x,y,z=1,*args):
print(x,y,z,args)
func(1,2,3,4,5,6,7)
>>1 2 3 (4,5,6,7)
#这里起作用的就是*号,相当于溢出的位置参数赋值给了它后面的变量,即args=(4,5,6,7)
2.2 可变长度的关键字参数
如果在最后一个形参名前加**号,那么在调用函数时,溢出的关键字参数,会以字典的形式保存下来赋值给形参。
def func(x,**kwargs):
print(x)
print(kwargs)
func(x=1,y=2,z=3)
>>1
>>{'y':2,'z':3}
#同上此时相当于把溢出的关键字实参一,y,z都被**接收以字典的形式赋值给kwargs,即kwargs={'y':2,'z':3}
2.3 组合使用
可变参数*args与关键字参数kwargs通常是组合在一起使用的,如果一个函数的形参为上述两种类型,那么代表该函数可以接收任何形式,任意长度的参数。
def wrapper(*args,**kwargs):
pass
在该函数内部还可以把接受到的实参传给另一个函数,这在后面推导装饰器时大有用处。
def func(x,y,z):
print(x,y,z)
def wrapper(*args,**kwargs):
func(*args,**kwargs)
wrapper(1,y=2,z=3)
>>1 2 3
分析:
此处在给wrapper传参时,其遵循的事函数func的参数规则,第一步,位置参数1被接受,以元组形式保存下来赋值给args,即args=(1,),关键字参数y=2,z=3被**以字典形式接收赋值给kwargs,即kwargs={'y':2,'z':3};第二步,执行func(args,kwargs),即func((1,),{'y':2,'z':3}),等同于func(1,y=2,z=3)。
3、函数对象和闭包
函数对象指的是函数可以被当做"数据"来处理,具体可以分为四个方面的使用
3.1 函数可以被引用
def add(x,y):
return x+y
func = add
func(1,2)
>>3
3.2 函数可以作为容器类型的元素
dic = {'add':add}
>>dic
>>{'add': <function add at 0x100661e18>}
>>dic['add'](1,2)
>>3
3.3 函数可以作为参数传入另一个函数
def foo(x,y,func):
return fun(x,y)
>>foo(1,2,add)
>>3
3.4 函数的返回值可以是一个函数
def bdd():
return add
func=bdd()
func(1,2)
>>3
3.5 闭包函数有两个关键点
"闭":值得时函数定义在另一个函数内即内嵌函数。
"包":指的是该函数包含对外层函数作用于变量的引用。
def f1():
x = 1
def f2():
print(x)
f2()
#此时f2就是内嵌函数,为‘闭’,f2有对外层变量x的引用,为‘包’
#但是我们不想在内部调用f2函数该怎么办呢
#这个时候函数对象的引用,可以作为返回对象就可以解决,即:
def f1():
x = 1
def f2():
print(x)
return f2 #注意不能加括号,否者就是返回f2的执行结果,我们需要的是他的内存地址以供在外部可以随时调用
f = f1() #此刻变量f接受到的就是f2的内存地址
总结:
闭包函数提供了一种新的为函数体传参的方式,为了给f2传值,在他的同级作用域给了他一个值,f2在整体缩进,外层再给他嵌套一个函数f1包起来。此时f1从原来的全局变成了局部,为了使我们在全局依然可以调用它,通过return函数对象再返回到全局。
三、什么是装饰器
上边讲了这么多,可能大家有点疑惑怎么还不介绍装饰器。不用急,这也是我们在学习中常犯的错误,急于求成反而不利于对知识的吸收好消化理解。其实在潜移默化中,我们已经把大部分构成装饰器的基本知识提到了,只是还未进行归纳整理。下面我们又将重新一步一步推导它的由来。
定义:定义一个函数(类),该函数专门用来为其他函数(对象)添加额外的功能。
装饰器本质上是一个python函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志,性能测试,事务处理,缓存,权限校验等场景。
四、为什么用装饰器
我们在为一个对象添加新功能时,往往秉持着开放封闭原则。
- 开放:指的是对拓展功能是开放的
- 封闭:指的是修改源代码是封闭的
即在不修改被装饰对象源代码和调用方式的情况下为被装饰对象新增功能。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。
五、装饰器的推导
提出需求:为index函数新增计算代码运行时间的功能,必须符合开放封闭原则。
import time
def index(x,y):
time.sleep(2)
print(f'来自index的{x}和{y}')
1、方案一
def index(x, y):
start = time.time()
time.sleep(2)
print(f"来自index的{x}和{y}")
stop = time.time()
print(stop-start)
结果:虽然实现了功能,但破环了开放封闭原则,修改了源代码,不符合要求,失败。
2、方案二
start = time.time()
index(1, 2)
stop = time.time()
print(stop-start)
结果:上述代码虽然没有修改源代码,也实现了功能,但是每次使用都要加上这三行代码,太过冗余,失败。
3、方案三
'''在方案二的基础上进行优化,为了解决代码冗余,我们把它写成一个函数'''
def wrapper():
start = time.time()
index(1, 2)
stop = time.time()
print(stop-start)
wrapper()
结果:此时我们不用每次加上三行代码,只需调用wrapper函数即可,但是复用性依然不够,可以在进行优化。
4、方案四
优化:解决index的传参被写死了的问题
#此时函数参数的知识就用上了
def wrapper(a, b):
start = time.time()
index(a, b)
stop = time.time()
print(stop-start)
wrapper(1,2)
#但是可能在后续的需求中index的传参个数会发生变化
#此时可变长度参数就能帮上大忙了
def wrapper(*args, **kwargs):
start = time.time()
index(*args, **kwargs)
stop = time.time()
print(stop-start)
'''这个时候就不用担心给他传参数的问题了,wrapper收到什么参数都会原封不动交给index函数'''
结果:进行了一系列的优化,我们发现虽然传参的问题解决了,但是这个时候index函数也写死了,以后的需求中不可能只有它需要这个功能,复用性不够,因此可以继续优化。
5、方案五
优化:index写死了的问题
'''我们知道一旦某个变量写死了,那么我们就用一个变量去代替他,但是在wrapper函数中,index写成变量后,无法通过形参传给他,这个时候闭包函数就大显神威了,它就提供了一种给函数传参的方式'''
def outter(func):
#func = index #写活
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
stop = time.time()
print(stop-start)
return wrapper
f = outter(index)
'''返回的是wrapper的内存地址赋值给f,加个括号就是在调用wrapper,它的作用就是计算以函数对象传入其中的index的执行时间统计'''
# 为了不改变调用方式,在进行优化
# 可以把f = outter(index),为什么不可以赋值给index呢
# 最后:index = outter(index) 即wrapper的内存地址
index() # 此时对于函数的调用者来说,他没有变化,早就换了
6、方案五
优化:上面看是已经优化得差不多了,其实还是有漏洞,原函数index是没有返回值的,此时调用换掉之后的index之后,返回的时wrapper的内存地址,它并没有返回值,index()返回的是None,没有做到天衣无缝。这个时候就要用到我们上面讲到的函数对象的引用可以作为返回值,问题就迎刃而解了。
def outter(func):
#func = index #写活
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
stop = time.time()
print(stop-start)
return res
return wrapper
''' 我们把func函数的返回值通过return,在返回出来,当我们运行index()时,实际上就是在调用wrapper,此刻它是有返回值的,也就是func的返回值,这个时候才做到了天衣无缝'''
6、最终方案(推荐)
def outter(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
return res
return wrapper
这就是一个最简单的无参装饰器的模版,我们想要给某个对象也就是func函数添加新功能时直接在wrapper函数内部书写代码即可。
关于有参装饰器,此处由于篇幅限制就不在说明。有参也就说明我们的函数内部需要一个参数,无非就是两种方式,一种通过形参直接传入,另一种就是通过闭包函数直接包给它,此处肯定是利用闭包函数更合理。
六、感言
误打误撞,因为老师的一个课后作业任务,完成了本人的第一篇知识总结。刚开始是有点惊慌的,但是随之而来的是惊喜,虽然担心写的不够好,但也算是想给自己一个交代,一个好的开始。知识的持续分享总结能够促进我们持续的学习进步。
经过这次小小的分享,回到开头,我想说的就是学习一些高阶知识,当我们感到模模糊糊的时候,不妨回归本质,从最基础的原理对他进行分解,一步一步推导,往往能给到我们一种醍醐灌顶,意想不到的收获。最后希望同大家一道能通过这次的学习,提升自己,完成自己的初心。
辛丑伊始 奋斗不止
--黎潘
刚转行1年测试新手:学习Python编程经验实战分享的更多相关文章
- 学习 Python 编程的 19 个资源 (转)
学习 Python 编程的 19 个资源 2018-01-07 数据与算法之美 编译:wzhvictor,英文:codecondo segmentfault.com/a/119000000418731 ...
- Linux运维人员如何学习python编程
Linux运维人员如何学习python编程 从不会写代码,到自己独立能写代码解决问题 .这个问题很重要!盲目学习所谓的项目,最后 还是不会自己写代码解决问题.首先解决了独立能写代码解决问题,再通过项目 ...
- 新手学习.net编程计划-1
.NET是一个庞大的学习体系,对于新手来说会感觉无从下手.学习知识必须从入门的基础学起,才能更好地掌握.学习.net也是如此,最基础的莫过于了解.net平台,以及掌握.net的基础语法C#. 本计划是 ...
- 新手学习Python时常见的错误
最近学习Python,现在把一些常见的错误总结如下: 1)忘记在 if , elif , else , for , while , class ,def 声明末尾添加 :(导致 "Synta ...
- 学习Python编程的11个资源
用 Python 写代码并不难,事实上,它一直以来都是被声称为最容易学习的编程语言.如果你正打算学习 web 开发,Python 是一个不错的选择,甚至你想学游戏开发也可 以从 Python 开始,因 ...
- 学习Python编程的11个精品资源
本文由 伯乐在线 - atupal 翻译自 Alex Ivanovs.欢迎加入技术翻译小组.转载请参见文章末尾处的要求. 用 Python 写代码并不难,事实上,它一直以来都是被声称为最容易学习的编程 ...
- 学习Python编程的最好的几本书
读书是汲取某个特定学科的知识以及更深入的理解该学科的最好的方式.在这个科技世界,通晓计算机系统各个不同的技术领域是至关重要的.其中最重要的内容之一便是计算机程序语言.现今,计算机中存在许多不同类型的程 ...
- 学习Python编程技术的流程与步骤,自学与参加培训学习都适用
一.清楚学习目标 无论是学习什么知识,都要有一个对学习目标的清楚认识.只有这样才能朝着目标持续前进,少走弯路,从学习中得到不断的提升,享受python学习计划的过程. 虽然目前的编程语言有很多,但是 ...
- 新手学习Python第三方包库pip安装失败总结
这篇文章纯原创,是之前自己学习使用pyhton时遇到的问题,故在此记录一下. 问题与需求:用python下载第三方库或包的时候出错怎么办? 方法有一下三种,可以解决大部分的问题. 1.在cmd命令控制 ...
随机推荐
- 发布Jar包到maven中央仓库
什么是maven中央仓库 maven是java世界最流行的构建工具,构建内容囊括了一个java项目的整个生命周期.其中最重要的功能就是依赖管理,maven通过一个类似云的ftp站点统一管理所有java ...
- 三种远程部署war包检测
简介 远程部署漏洞属于服务器.中间件配置问题,攻击者可通过远程部署漏洞获取系统权限,远程部署漏洞经常出现在Tomcat.Jboss.Weblogic等web容器之上. 0x01 ### tomcat部 ...
- OSS对象储存
简介 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量.安全.低成本.高可靠的云存储服务. 使用流程 名词解释 Endpoint(访问域名) Ac ...
- 不使用map和set实现LRU——那用List?
遇到一道面试题,不使用map和set实现LRU,要求get的时间复杂度为O(logn),put的时间复杂度不超过O(n).想到了用ArrayList来实现,保存有序的key.然而牵涉add节点,在保证 ...
- 《吃透MQ系列》核心基础全在这里了
这是<吃透XXX>技术系列的开篇,这个系列的思路是:先找到每个技术栈最本质的东西,然后以此为出发点,逐渐延伸出其他核心知识.所以,整个系列侧重于思考力的训练,不仅仅是讲清楚 What,而是 ...
- POJ1458 Common Subsequence
题目链接:http://poj.org/problem?id=1458 分析:最大公共子序列模板 1 #include<iostream> 2 #include<sstream> ...
- mysql从一个表提取数据更新另外一个表(修复表数据的不一致)
目前碰到一个数据不一致的情况,有两张表,一张项目表,一张项目成员表,项目表有个字段是项目工作时间,是项目成员的工作时间汇总.是由于该了逻辑,所以要把数据改成一致. 项目表的大致结构如下. 表名:pro ...
- python带颜色打印字符串
python带颜色打印字符串 之前调试pwn题的时候,有时候需要将某些特别的,重要的信息用不一样的颜色打印出来.查阅一些资料,了解了print函数的特性后,自己写了一个脚本,可以用来获取带颜色信息的字 ...
- windows基线检测脚本编写指南-powershell版
前言: 因为工作的原因,要写windows下的基线检查脚本.之前没接触过,在网上找了半天也没找到现成的,无奈只好自己研究,最后还是成功完成了工作. 在我编写之后发现windows下的基线基本就是检 ...
- JAVA面试题:输出100以内所有的素数
转载:https://www.cnblogs.com/onway/archive/2012/11/15/2771912.html Java输出1-100中所有的素数 很多人笔试时都会遇到这个问题,小农 ...