装饰器是一个用来装饰其余函数的工具,即为其余函数添加附加功能,其本质就是函数。html
装饰器须要遵循的如下两个原则:python
一、若要新增一个功能,就不能再修改源代码,即不能再修改被装饰函数的源代码。算法
二、不能修改被装饰函数的调用方式。数组
实现装饰器的知识储备:app
一、函数即变量。ssh
二、高阶函数。函数
三、函数嵌套。工具
即要想实现装饰器,首先应对以上三条知识有必定了解。单元测试
首先咱们来简单介绍一下函数即变量这个概念。咱们举一个简单的例子来讲明:学习
def bar(): print('in the bar') def foo(): print('in the foo') bar() foo()
代码运行结果以下:
如今你们想象一下,若是把函数bar和函数foo的位置调换一下,还能运行吗?或者说运行结果仍是同样吗?
咱们试验一下:
def foo(): print('in the foo') bar() def bar(): print('in the bar') foo()
运行结果仍是同样。
这说明在python中函数可看作是一个变量,定义一个函数与定义一个变量x并没有二致,因此上述代码调换位置以后仍是能够运行的。
在写一个装饰器函数时,咱们也会用到高阶函数这个概念,它的主要思想就是把一个函数名当作实参传给另外一个函数,以实如今不修改要装饰程序的源代码的状况下为其添加新功能的做用。下面咱们举一个简单的例子进行说明。
def bar():
print('in the bar')
def test1(func):
print(func) #打印函数func的内存地址
func() #调用函数func
return (func) #返回函数func的内存地址
print(test1(bar)) #将函数bar做为实参传给func
上例经过函数test1来调用函数bar,咱们能够看到代码运行结果以下:
上面是一个简单的高阶函数的例子,你们能够简单看一下结构,下面咱们介绍一个具有简单功能的高阶函数的代码,以下:
import time def bar(): time.sleep(3) #bar函数实现延迟三秒输出的功能 print('in the bar') def test(func): start_time=time.time() func() end_time=time.time() print('the func run time is %s'%(end_time-start_time)) #为func函数添加一个计算函数执行时间的功能 return func #返回函数func的内存地址 bar=test(bar) bar()
代码执行结果以下:
咱们能够看到经过高阶函数test,咱们实现了为bar函数添加计算函数执行时间的功能,而且,经过把函数赋值给变量bar,咱们实现了不改变原函数调用方式的前提下增长新功能的做用。
这既是装饰器的核心所在。
在这一节咱们介绍嵌套函数,函数的嵌套在装饰器中也发挥着很是重要的做用,示例以下:
def foo(): print('in the foo') def bar(): print('in the bar') bar() foo()
代码执行结果以下:
经过上述例子咱们能够直观的看到局部做用域和全局做用域的访问顺序:先外后里。
经过以前的热身,如今咱们本身动手来写一个装饰器。
假设刚开始咱们有两个函数test1和test2,咱们想经过装饰器来添加一个计算函数运行时间的新功能,代码以下:
import time def timmer(func): def deco(*args,**kwargs): #这里用到参数组*args和**kwargs是针对可能出现func函数的形参个数不固定的状况而设定的 start_time=time.time() func(*args,**kwargs) end_time=time.time() print('the func run time is %s'%(end_time-start_time)) #计算函数func实际运行时间 return deco @timmer #test1=timmer(test1) def test1(): time.sleep(3) print('in the test1') @timmer #test2=timmer(test2) def test2(name): print('test2:',name) test1() #因以前@timmer操做,这里实际调用的是deco test2('abcd') #同理,这里调用的也是deco
这里,你们须要格外注意的是为了简洁,python中能够用@timmer来代替test1=timmer(test1),也就是上面高阶函数讲到的不改变函数调用方式。
代码运行结果以下:
咱们能够看到装饰器圆满完成了“装饰”的功能。
学习完上述简单的装饰器程序,咱们再挑战一下高难度的——咱们用装饰器来写一个为某些网站设置登录界面的代码,具体以下:
user,passwd='kobe','0824' #初始化用户名和密码 def auth(func): def wrapper(*args,**kwargs): username=input('username:').strip() password=input('password:').strip() if user==username and passwd==password: #验证用户名和密码是否正确 print('\033[32;1muser has passed authentication\033[0m') res=func(*args,**kwargs) print('--after authentication') return res else: exit('\033[31;1minvalid username or password\033[0m') #错误提示 return wrapper def index(): print('welcome to index page') @auth def home(): print('welcome to home page') return 'from home' @auth def bbs(): print('welcome to bbs page') index() print(home()) bbs()
代码运行结果以下:
首先进入一个home用户登陆界面,用于输入用户名和密码:
若输入错误的用户名和密码,则有:
若输入正确的用户名和密码,则会显示欢迎登陆home界面,并进入登陆下一个bbs界面时所需输入用户名和密码的界面,以下:
咱们再输入以前的正确的用户名和密码,则会进入欢迎登陆bbs界面的相关信息,以下:
以上咱们就用装饰器实现了为某些网站设置登录界面的功能。
在学习迭代器前我先向你们介绍一下列表生成式。
假如咱们想要输出0到20间全部的偶数,咱们能够用列表生成式以下:
>>>a=[i*2 for i in range(11)]
>>>a
[0,2,4,8,10,12,14,16,18,20]
咱们能够看到,经过列表生成式咱们能够直接建立一个列表。可是,受到内存限制,列表的容量确定是有限的。并且建立一个包含100个元素的列表不只占用很大的存储空间,并且若是咱们仅仅须要访问前面几个元素,那后面绝大多数元素占用的空间就白白浪费啦。因此,若是列表元素能够按照算法推算出来,name咱们是否能够在循环过程当中不断推算出后续的元素呢?这样的话就不用建立一个完整的list,从而节省大量的空间。在python中,这种一边循环一边计算的机制称为生成器(generator)。
若咱们想用生成器来输出0到20间全部的偶数,咱们将上述列表生成式的中括号 [ ] 换成小括号 ( ) 便可,代码以下:
>>>a=(i*2 for i in range(11)) >>>a
<generator object <genexpr> at 0x0000027EAA3FE410>
咱们能够看到,当咱们按照以前列表生成式的方式来输出元素时,却提示建立了一个生成器generator——<generator object <genexpr> at 0x0000027EAA3FE410>。
那么,若咱们想要输出生成器中的元素时应该怎么办呢?这里就要用到next ( ) 方法,以下:
>>>a=(i*2 for i in range(11)) >>>a <generator object <genexpr> at 0x0000027EAA3FE410> >>>a.__next__() 0 >>>a.__next__() 2 >>>a.__next__() 4
从上述的调用过程咱们能够看到,只有在调用时才会生成相应的数据,,这样也就能节省大量的空间。并且注意的是,咱们在调用生成器中的元素用到next()方法时,只能不断地向下一级生成数据而不能返回上一级。
以上咱们只是举了一个简单的生成偶数的例子,若要推算的算法比较复杂,还能够用到函数。好比咱们想要生成一个Fibonacci数列,
注:Fibonacci数列的定义可参见:https://baike.baidu.com/item/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97
咱们就能够用函数来完成,代码以下:
def fib(max): n,a,b=0,0,1 while n<max: print(b) a,b=b,a+b n=n+1 return 'done' fib(10)
上述代码能够输出前十个fibonacci数列的元素,以下:
那若是咱们想以生成器的形式来输出前十个fibonacci数列的元素应该怎么作呢?
其实很简单,就是把上述代码函数中的 print(b)换成 yield b,并用next()方法来输出元素便可,代码以下:
def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n=n+1 return 'done' f=fib(10) print(f.__next__()) print(f.__next__()) print('-----------') for i in f: print(i)
代码运行结果以下:
可直接做用于for循环的有如下几种:
一是集合数据类型,如列表list,元组tuple,字典dict,集合set,字符串str等。
二是generator,包括生成器和带yield的generator function。
这些能够直接做用于for循环的对象统称为可迭代对象:Iterrable。
咱们可使用isinstance()判断一个对象是否为Iterable对象,例如:
>>>from collections import Iterable >>>isinstance('abc',Iterable) #判断字符串'abc'是否为Iterable对象 True >>>isinstance({},Iterable) #判断{}是否为Iterable对象 True
而生成器不但能够做用于for循环,还能够被next函数不断调用并返回下一个值,直到最后抛出StopIteration错误,即表示没法返回下一个值了。
咱们称可被next()方法调用并不断返回下一个值的对象统称为迭代器:Iterator。
以列表为例,咱们能够用dir()方法来查看某对象可以使用的方法:
>>> a=[1,2,3] >>> dir(a) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__',
'__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__',
'__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
咱们能够看到列表a不能使用next()方法,故不是迭代器。
咱们也能够用刚才的isinstance方法来验证:
>>> a=[1,2,3] >>> dir(a) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] >>> from collections import Iterator >>> isinstance(a,Iterator) False
咱们能够看到列表并非一个迭代器。
经过上节的学习,生成器可使用next()方法来不断调用下一个元素,那生成器应该就是一个迭代器,验证以下:
>>> from collections import Iterator >>> isinstance((x for x in range(5)),Iterator) True
那么,咱们一样也可使用其余方法来使非迭代器对象转化为迭代器对象,这里就须要用到iter函数,示例以下:
>>> a=[1,2,3] >>> b=iter(a) >>> dir(b) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__',
'__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] >>> isinstance(b,Iterator) True
迭代器的好处是它能够表示一个无限大的数据流,如全体天然数,而list是不可能存储全体天然数的。
关于生成器和迭代器总结以下:
一、凡是能够做用于for循环的对象都是Iterable类型。
二、凡是能够做用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列。
三、集合数据类型如list,dict,str,等都是Iterable类型但不是Iterator类型,不过能够经过iter函数得到一个Iterator类型对象。
python解释器有许多可用的内置函数和类型,它们按字母顺序排列以下:
它们的用法可参见https://docs.python.org/3/library/functions.html?highlight=built#ascii,在这里咱们就很少作介绍了。
完成一个项目时,设计好软件目录结构规范是很是重要的。目录结构规范化能够更好地控制程序结构,让程序具备更高的可读性,而且可维护性更高。
下面介绍一下个人老师教给个人一种目录组织方式,以下:
假设咱们要完成的项目名为foo,最方便快捷的目录结构能够写为:
Foo/ |-- bin/ | |-- foo | |-- foo/ | |-- tests/ | | |-- __init__.py | | |-- test_main.py | | | |-- __init__.py | |-- main.py | |-- docs/ | |-- conf.py | |-- abc.rst | |-- setup.py |-- requirements.txt |-- README
下面简单解释一下:
一、bin/
: 存放项目的一些可执行文件,固然你能够起名script/
之类的也行。
二、foo/
: 存放项目的全部源代码。(1) 源代码中的全部模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/
存放单元测试代码; (3) 程序的入口最好命名为main.py
。
三、docs/
: 存放一些文档。
四、setup.py
: 安装、部署、打包的脚本。
五、requirements.txt
: 存放软件依赖的外部Python包列表。
六、README
: 项目说明文件。
关于readme内容,咱们须要注意的是,它应该包含如下五方面内容:
一、软件定位,软件的基本功能。
二、运行代码的方法,安装环境,启动命令等。
三、简要的使用说明。
四、代码目录结构说明,软件的基本原理。
五、常见问题说明。