一、装饰器
1)本质就是函数,(装饰其他函数),为其他函数添加附加功能
2)原则:
1.不能修改被装饰的函数源代码
2.不能修改被装饰的函数的调用方式
实现装饰器知识储备:
1.函数即”变量“
2.高阶函数
3.嵌套函数
高阶函数+嵌套函数=装饰器
实现装饰器知识储备:
先看一个装饰器的例子,反正没学习下面的内容之前,我是看不懂:


import time #定义装饰器 def timmer(func): def warrper(*args,**kwargs): start_time=time.time() func() stop_time=time.time() print("the func run time is %s"%(stop_time-start_time)) return warrper @timmer def test1(): time.sleep(3) print("in the test1") test1()
如何一步一步实现装饰器:
1.函数即“变量”
有了上面的图,看下面的例子应该easy很多
1
2
3
4
5
|
def foo(): print ( "in the foo" ) bar() foo() 报错bar没有定义 |
1
2
3
4
5
6
7
|
def bar(): print ( "in the bar" ) def foo(): print ( "in the foo" ) bar() foo() 正常运行 |
1
2
3
4
5
6
7
|
def foo(): print ( "in the foo" ) bar() def bar(): print ( "in the bar" ) foo() 正常运行 |
运行该例子的时候,先将定义的函数foo、bar依次存储到了内存;
然后在运行foo(),调用bar是在内存中存在的,所以不会报错。
1
2
3
4
5
6
7
|
def foo(): print ( "in the foo" ) bar() foo() def bar(): print ( "in the bar" )
会报错提示bar没有定义 |
执行foo(),先将foo函数存储到内存,然后就运行foo;
而此时bar函数还没有定义写到内存中,找不到bar所以会报错。
总结:
定义一个函数就相当于将函数体赋值给函数名;
定义变量/函数 是先定义,再执行;
函数和变量一样都会被python内存回收机制回收;
所以:函数即“变量”
1.1匿名函数
#函数没有名字,下面给函数定义了一个变量名来引用;
1
2
|
niming = lambda x:x * 3 print (niming( 3 )) |
2.高阶函数
a:把一个函数名当做实参给另外一个函数;
(在不修改被装饰函数源代码的情况下为其添加功能)
b:返回值中包含函数名;
(不修改函数的调用方式)
把函数当作一个实参传递给形参。
看一个例子:
def bar(): print("in the bar") def test1(func): print(func) func() test1(bar)#等于func=bar,func() bar是一个地址,相当门牌号func()就是bar()
稍作改变:
import time def bar(): time.sleep(0.3) print("in the bar") def test1(func): start_time=time.time() func() stop_time=time.time() print("the func run time is %s" %(stop_time-start_time)) test1(bar)#等于func=bar,func() bar是一个地址,相当门牌号func()就是bar()
总结:(在不修改被装饰函数源代码的情况下为其添加功能),为bar这个函数计算了执行时间,但是呢却改变了bar的调用方式,这个不是我们想要的,我们继续下一步的探索
import time def bar(): time.sleep(0.3) print("in the bar") def test2(func): print(func) return func #运行1 #print(test2(bar))#最后打印的是bar的地址 #运行2 #t = test2(bar) #t()#把bar的地址给了t,t()就是执行bar这个函数 #运行3 bar=test2(bar) bar()#把t换成bar,就是执行bar这个函数,即没有改变函数的调用方式 #bar是原函数
总结:返回值中包含函数名; (不修改函数的调用方式),这个需要大家一步一步的执行,就比较好理解了
写一个装饰器(高阶函数)
import time def deco(func): star_time= time.time() return func stop_time=time.time() print("th func run time is %s"%(stop_time-star_time)) def test1(): time.sleep(3) print("in the test1") def test2(): time.sleep(5) print("in the test2") test1=deco(test1) test1() test2=deco(test2) test2() #结束时间没有运行;
虽然没有改变函数的调用方式,发现结束时间没有执行,装饰的效果没有实现,依旧是一个高阶函数
3.嵌套函数
高阶函数+嵌套函数= 装饰器
所以加上嵌套函数,就等于装饰器了,一步步终于要看到装饰器了
import time def timmer(func):#timmer(test1) func = test1 def deco(): star_time= time.time() func()#run test1 stop_time=time.time() print("th func run time is %s"%(stop_time-star_time)) return deco def test1(): time.sleep(0.3) print("in the test1") test1=timmer(test1)# timmer(test1)是deco的内存地址 test1()
装饰器雏形就这样写出来了,再稍作修改,就是真正的装饰器
import time def timmer(func):#timmer(test1) func = test1 def deco(): star_time= time.time() func()#run test1 stop_time=time.time() print("th func run time is %s"%(stop_time-star_time)) return deco @timmer # test1=timmer(test1)# timmer(test1)是deco的内存地址 def test1(): time.sleep(0.3) print("in the test1") test1()
再修改一点,就变成了高级的装饰器了
import time def timmer(func):#timmer(test1) func = test1 def deco(*args,**kwargs): star_time= time.time() func(*args,**kwargs)#run test1 stop_time=time.time() print("th func run time is %s"%(stop_time-star_time)) return deco @timmer # test1=timmer(test1)# timmer(test1)是deco的内存地址 def test1(*args,**kwargs): time.sleep(0.3) print("in the test1",9999) test1(0,8,'ioio',{"name":"cathy"})
可以传递任何不固定个数和字典型的参数了
下面就使用装饰器做个例子:
需求:网站有一些页面需要登录才能浏览,一些不需要浏览
usrename = "cathy" password = "123abc" def auth(func): def warpper(*args,**kwargs): user = input("input username:") passwd = input("input password:") if usrename == user and password == passwd: res = func(*args,**kwargs)#想要有返回值,这里也可以直接return print("-------center----") return res else: exit("wrong") return warpper #而这里没有返回值;在上边return res之后就有数据了 def index(): print("in the index") @auth #当home有return结果的时候,用上边的无法获取到home return的结果;wrapper加一个return; def home(): print("in the home") return "form home" @auth def bbs(): print("in the home") index() print(home())#调用home相当于调用wrapper bbs()
需求升级终极版装饰器,bbs需要用ldap验证方式
usrename = "cathy" password = "123abc" def auth(loal_type): def out_warp(func): def warpper(*args,**kwargs): if loal_type=="local": user = input("input username:") passwd = input("input password:") if usrename == user and password == passwd: res = func(*args,**kwargs)#想要有返回值,这里也可以直接return print("-------center----") return res else: exit("wrong") elif loal_type == "ldap": print("what is it?") return warpper #而这里没有返回值;在上边return res之后就有数据了 return out_warp def index(): print("in the index") @auth(loal_type = "local") #当home有return结果的时候,用上边的无法获取到home return的结果;wrapper加一个return; def home(): print("in the home") return "form home" @auth(loal_type = "ldap") def bbs(): print("in the home") index() print(home())#调用home相当于调用wrapper bbs()
二、生成器
列表生成式:
a = [i*2 for i in range(10)]
使代码更简洁、还可以执行一个函数;
[i*2 for i in range(10)]
相当于以下三句代码:
a = [] for i in range(10): a.append(i*2)
通过列表生成式,我们可以直接创一个列表。但是受到内存限制,列表容量肯定是有限的。而且创建一个包含100万个元素的列表,
不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那么后面绝大多数元素占用的空间是白白浪费的。
生成器定义
如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,
从而节省大量的空间。在python中,这种一边循环一边计算的机制,称为生成器:generator。
生成器创建方法
要创建一个generator,有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
生成器内容深化
生成器被调用的时候才生成所以列表元素
a= (i*2 for i in range(10)) print(a) <generator object <genexpr> at 0x000002354C255AF0>
访问前两个数据:
a= (i*2 for i in range(10)) print(a) print(a.__next__()) print(a.__next__())
<generator object <genexpr> at 0x00000270E78E5AF0>
0
2
生成器只记录当前位置,只存储一个数据;
只有一个__next__()方法。在python2.0 里边是next()
取数据可以用for循环取数据;
当推算的算法比较复杂的时候,用for循环无法实现的时候,用函数来实现。
fib例子:
先看一下斐波那契数列Fibonacci简写fib,除了第一个和第二个数外,任意一个数都可以由前两个数相加得到;
def fib(max): n,a,b = 0,0,1 while n < max: print(b) a,b = b,a+b #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1] n = n+1 return "----done-----" fib(10)
上面的函数和generator仅一步之遥,只需要把print(b)改成yield b 就可以了:
def fib(max): n,a,b = 0,0,1 while n < max: yield b a,b = b,a+b #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1] n = n+1 return "----done-----" f = fib(10) print(f) print(f.__next__()) print(f.__next__()) print(f.__next__())
当f.__next__取到第11个数据的时候,已经没有数据了,程序会报出一个错误done;
如果不想报错,就抓住这个异常。
def fib(max): n,a,b = 0,0,1 while n < max: yield b a,b = b,a+b #相当于t = (b,a+b) #t是一个tuple,a = t[0],b = t[1] n = n+1 return "done" g = fib(3) print(g) while True: try: x= next(g) print("g:",x) except StopIteration as e: print("generaotr return value:", e.value) break print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__())
通过装饰器可以实现并行效果,使单线程也可以并发。
通过yield实现在单线程的情况下实现并发运算的效果。
例子:典型的生产者消费者模型
import time #消费者 def consumer(name): #consumer 消费者 print("%s 准备吃包子啦!"%name) while True: baozi = yield print("包子[%s]来了,被[%s]吃了!"%(baozi,name)) #c = consumer("123") #只是变成一个生成器,而不执行;如果是函数是会被调用的。 #c.__next__() #调用 # c.__next__() #c.send("韭菜") def producer(name): c = consumer("A") c2 = consumer("B") c.__next__() c2.__next__() print("开始准备吃包子啦!") for i in range(3): time.sleep(1) print("%s 做了1个包子,分两半!"%name) c.send(i) c2.send(i) producer("Cathy")
三、迭代器
这里面讲的迭代器是一个定义,具体使用场景后面或许用到,一般不用
可以直接作用for循环的数据类型
我们已经知道,可以直接作用for循环的数据类型有以下几种:
一类是集合数据类型 如:list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function;
可迭代对象定义
这些可以直接作用for循环的对象称为可迭代对象:Iterable。
可迭代对象判断
可以使用isinstance()判断一个对象是否是Iterable对象:
1
2
3
4
5
6
7
|
from collections import Iterable,Iterator isinstance ([],Iterable) print ( isinstance ([],Iterable)) isinstance ({},Iterable) isinstance ( "abc" ,Iterable) isinstance ((x for x in range ( 10 )),Iterable) isinstance ( 100 ,Iterable) #返回False |
迭代器定义
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator 。
1
2
|
isinstance ((x for x in range ( 10 )),Iterator) print ( isinstance ((x for x in range ( 10 )),Iterator)) |
把list、dict、str转成迭代器
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
1
2
3
4
|
isinstance ( iter ([]),Iterator) print ( isinstance ( iter ([]),Iterator)) isinstance ( iter ( "abc" ),Iterator) print ( isinstance ( iter ( "abc" ),Iterator)) |
为什么list、dict、str等数据类型不是Iterator?
这是因为python的Iterator对象表示的事一个数据流,Iterator对象可以被next()函数调用并不断返回一个序列,
但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,
只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结:
凡是可作用域for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如:list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
python的for循环本质上就是通过不断调用next()函数实现的,
1
2
3
|
range ( 10 ) python3.x 就是一个迭代器 python2.x 就生成一个列表,要成为迭代器写成 xrange ( 10 ) ; |
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
for i in [ 1 , 2 , 3 , 4 , 5 ]: pass 就相当于下边: - - - - - - - - - - - - - - - - - - - 首先获得迭代器 it = iter ([ 1 , 2 , 3 , 4 , 5 ]) 循环 while True : try : #获得下一个值 x = next (it) except StopIteration: #遇到StopIteration break - - - - - - - - - - - - - - - - - - - - - - |
pickle 用于python特有的类型 和 python的数据类型间进行转换
序列化:把字典或者字符串的内存对象 存到硬盘上;
反序列化:就是从硬盘上加载出来
import json info = { "name" : "aaa", "age" : 22 } f = open("test.text","w",encoding="utf-8") f.write(json.dumps(info)) f.close()
反序列化:就是从硬盘上加载出来
import json f = open("test.text","r",encoding="utf-8") data = json.load(f) #data = json.loads(f.read()) print(data['age']) f.close()
pickle序列化和反序列化
序列化:把字典或者字符串的内存对象 存到硬盘上;
# Author Cathy import pickle def hello(name): print('hello123',name) info = { "name" : "aaa", "age" : 22, "new" : hello } f = open("test.text","wb") f.write(pickle.dumps(info)) f.close()
反序列化:就是从硬盘上加载出来
# Author Cathy import pickle def hello(name): print('hello567', name) f = open("test.text","rb") data = pickle.load(f) #data = json.loads(f.read()) print(data)#结果是{'name': 'aaa', 'age': 22, 'new': <function hello at 0x000002001C732E18>} print(data['age'])#结果是22 ##运行hello 函数,查看显示结果,这里只是表现了一种写代码的方式。 hello(data["name"])#结果是hello567 aaa f.close()
json多次dumps和多次loads
序列化:把字典或者字符串的内存对象 存到硬盘上;
import json info = { "name" : "aaa", "age" : 22 } f = open("test01.text","w") f.write(json.dumps(info)) info["age"] = 30 f.write(json.dumps(info)) f.close() ''' test01.text内容如下: {"name": "aaa", "age": 22}{"name": "aaa", "age": 30} '''
反序列化:就是从硬盘上加载出来 ,在这里loads的时候会报错。
import json f = open("test01.text","r") data = json.loads(f.read()) print(data["age"]) f.close()
当学序列化只dumps一次,在loads就正确了
序列化 import json info = { "name" : "aaa", "age" : 22 } f = open("test01.text","w") #f.write(json.dumps(info)) info["age"] = 30 f.write(json.dumps(info)) f.close() #反序列化 import json f = open("test01.text","r") data = json.loads(f.read()) print(data["age"]) #结果是:30 f.close()
总结:
json可以dumps多次,loads的时候报错;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。
pickle多次dumps和多次loads
序列化 import pickle info = { "name" : "aaa", "age" : 22 } f = open("test001.text","wb") f.write(pickle.dumps(info)) info["age"] = 30 f.write(pickle.dumps(info)) f.close() 反序列化 f = open("test001.text","rb") data = pickle.loads(f.read()) print(data["age"]) #结果是:22 f.close()
总结:
pickle可以dumps多次,loads的时候不报错,但得出的结果是第一次dumps的内容;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。
总结:
json 用于字符串和python数据类型间进行转换
pickle 用于python特有的类型 和 python的数据类型间进行转换
序列化:把字典或者字符串的内存对象 存到硬盘上;
反序列化:就是从硬盘上加载出来
当json dumps多次的时候,loads的时候报错;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。
当pickle dumps多次的时候,loads的时候不报错,但得出的结果是第一次dumps的内容;同时也不可以loads多次;
要想loads多次,就在dumps的时候dumps成不同的文件,laods不同文件。
软件开发规范
1、可读性高
2、可维护性高
目录组织方式:
FOO/ 项目名
bin/ 可执行文件放的目录
foo 可执行文件名字,当启动foo的时候调用foo/下的main.py;
conf 配置文件
foo/ 主程序目录,源代码中的所有模块、包都应该放在此目录。
tests/ 存放单元测试代码;
__init__.py pycharm自动创建,是个空文件;
main.py 程序主入口,
docs 存放文档
conf.py
abc.rst
setup.py 安装、部署、打包的脚本。
requirements.txt 存放软件依赖的外部Python包列表。
README 项目说明文件。
除此之外,有一些方案给出了更加多的内容。比如LICENSE.txt,ChangeLog.txt文件等,我没有列在这里,因为这些东西主要是项目开源的时候需要用到。
关于README的内容
这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
它需要说明以下几个事项:
1.软件定位,软件的基本功能。
2.运行代码的方法: 安装环境、启动命令等。
3.简要的使用说明。
4.代码目录结构说明,更详细点可以说明软件的基本原理。
5.常见问题说明。
在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。
可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。
关于requirements.txt和setup.py
setup.py
一般来说,用setup.py来管理代码的打包、安装、部署问题。业界标准的写法是用Python流行的打包工具setuptools来管理这些事情。
这种方式普遍应用于开源项目中。不过这里的核心思想不是用标准化的工具来解决这些问题,而是说,
一个项目一定要有一个安装部署工具,能快速便捷的在一台新机器上将环境装好、代码部署好和将程序运行起来。
setuptools的文档比较庞大,刚接触的话,可能不太好找到切入点。学习技术的方式就是看他人是怎么用的,
可以参考一下Python的一个Web框架,flask是如何写的: setup.py https://github.com/mitsuhiko/flask/blob/master/setup.py
当然,简单点自己写个安装脚本(deploy.sh)替代setup.py也未尝不可。
requirements.txt
这个文件存在的目的是:
方便开发者维护软件的包依赖。将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包。
方便读者明确项目使用了哪些Python包。
这个文件的格式是每一行包含一个包依赖的说明,通常是flask>=0.10这种格式,要求是这个格式能被pip识别,
这样就可以简单的通过 pip install -r requirements.txt来把所有Python包依赖都装好了。具体格式说明: 点这里。
https://pip.readthedocs.org/en/1.1/requirements.html
关于配置文件的使用方法
注意,在上面的目录结构中,没有将conf.py放在源码目录下,而是放在docs/目录下。
很多项目对配置文件的使用做法是:
配置文件写在一个或多个python文件中,比如此处的conf.py。
项目中哪个模块用到这个配置文件就直接通过import conf这种形式来在代码中使用配置。
这种做法不太好的方面:
这让单元测试变得困难(因为模块内部依赖了外部配置)
另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径。
程序组件可复用性太差,因为这种贯穿所有模块的代码硬编码方式,使得大部分模块都依赖conf.py这个文件。
所以,更好的方式是,
模块的配置都是可以灵活配置的,不受外部配置文件的影响。
程序的配置也是可以灵活控制的。
能够佐证这个思想的是,用过nginx和mysql的同学都知道,nginx、mysql这些程序都可以自由的指定用户配置。
所以,不应当在代码中直接import conf来使用配置文件。上面目录结构中的conf.py,是给出的一个配置样例,
不是在写死在程序中直接引用的配置文件。可以通过给main.py启动参数指定配置路径的方式来让程序读取配置内容。
当然,这里的conf.py你可以换个类似的名字,比如settings.py。或者你也可以使用其他格式的内容来编写配置文件,
比如settings.yaml之类的。
不同目录间模块调用
import os
import sys
BASE_DIR= os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_DIR)
from xxx import xxx