python自动化编程-第四天
python自动化编程-第四天 装饰器 生成器 内置函数
一、python的内存回收机制
我们都是知道变量名就是给内存地址附加上一个名字,这样我们在使用变量时,python就会根据变量名,返回对应内存地址内的数据;而变量实际上就是对内存地址的引用,每次对内存地址的引用在python内部都会有一个计数器,就是“引用计数”。
python中的垃圾回收机制,也是根据"引用计数"来进行的;当第一次给变量赋值时,该内存地址的”引用计数“就会加1,引用的次数越多,”引用计数“的值也就会随之增加;每多引用一次内存地址,那么该内存地址的“引用计数”就会增加1;一般情况下使用python内置的方法del来删除变量名;这样该内存地址的“引用计数”就会减1;一旦该内存地址的“引用计数”为0,python就会将这段内存地址释放;也就是垃圾回收。
二、匿名函数
通常在定义函数时,首先都要一个关键字def,然后定义一个函数名和一些形参;最后才是函数体;
而匿名函数就是不需要关键字def和函数名;而是需要关键字lambda和参数,然后直接定义函数体即可;
lambda语句(匿名函数)中,冒号前是参数,可以有多个,用逗号隔开,冒号右边的是返回值。
匿名函数只能处理一些较为简单的数据,如三元表达式;不支持较为复杂的逻辑运算(循环);
def <lambda>(arguments):
return expression
例子:
>>> (lambda n:n*2)(2)
4
>>> c = lambda n:n*2
>>> c(2)
4
>>> def d(n):
... return n*2
...
>>> d(2)
4
从上面的例子中可以看到,匿名函数通常无法保存,运行结束后,内存地址就会回收;如果需要保存就要将匿名函数赋值给一个变量名,这样就和通常定义的函数没有区别了;
匿名函数通常与其他函数混合使用比较常见:
#reduce函数
>>> import functools
>>> sum = functools.reduce(lambda x,y:x+y,(1,2,3))
>>> print(sum)
6
# reduce为逐次执行第二个参数(可迭代对象)里的每项,每次接收的参数为2个,最后返回一个结果。
# 第一次x = 1,y = 2,得出的结果为3,将这个3赋值给x,再从后面list中取下一个参数3,赋值给y;
# 对于序列内所有元素进行累计操作
# reduce的第一个参数,函数必须要有两个参数
# reduce的第二个参数,要循环的序列
# reduce的第三个参数,初始值
#map函数
>>> list1 = map(lambda n:n*2,range(5))
>>> list1
<map object at 0x103170240>
>>> for i in list1:
... print(i)
...
0
2
4
6
8
# 将函数应用于第二个参数(可迭代对象)的每个项,然后在返回一个迭代器, 类似于[ i*2 for i in range(5)]
# 遍历序列,对序列中每个元素进行操作,最终获取新的序列
>>> li = [11, 22, 33]
>>> sl = [1, 2, 3]
>>> new_list = map(lambda a, b: a + b, li, sl)
#filter函数
>>> res = filter(lambda n:n<5,range(10)) #将小于5的数字打印出来
>>> for i in res:
... print(i)
...
0
1
2
3
4
# 具体语法:filter(函数名,可迭代对象),如果可迭代对象传到函数中,结果为True,那么结果就是一个列表
# 对于序列中的元素进行筛选,最终获取符合条件的序列
# filter第一个参数为空,将获取原来序列
三、装饰器
1、装饰器的定义
装饰器本质上也是一个函数。
功能:装饰其他函数,也就是为其他函数添加额外功能;
2、装饰器的作用
- 不修改被装饰的函数的源代码;
- 不修改被装饰的函数的调用方式;
意思就是装饰器对被它装饰的函数是完全透明的。
3、实现装饰器的知识储备:
- 函数即“变量”
- 高阶函数
- 嵌套函数
①.函数即“变量”
变量在定义时,其实就是将内存地址附加一个门牌号,这样在调用变量时,就会根据这个门牌号找到这段内存地址,从而取出其值;而函数在定义时与变量是一样的处理方式;不同的地方在于函数的“值”是函数体;

例如lambda函数,在不给lambda函数赋值给一个变量时,那么该lambda函数下一次使用时就需要重新定义,但是如果将lambda函数赋值一个变量,那么下一次在使用该lambda函数时,只需要调用这个变量名即可;
>>> (lambda x:x*2)(2)
4
>>> c = lambda x :x*2
>>> c(2)
4
>>> c
<function <lambda> at 0x103a61e18>
函数可以理解为将lambda函数赋值给了一个变量名,这个变量名就是函数名;
>>> def b(x):
... return x*2
...
>>> b(2)
4
>>> b
<function b at 0x103d669d8>
这样我们就可以理解为函数名其实就是调用了内存地址中的函数体,但是它不会执行;要执行的话就需要在函数名后面加上一对小括号并传递相应的参数即可;
②.高阶函数
有两种函数被称之为高阶函数:
1.把一个函数名当做一个实参传给另外一个函数;
2.返回值中包含函数名(不修改函数的调用方式)
首先来说下第一种将函数名当做实参传递给另一个函数的作用:
import time
def test1(func):
start_time = time.time()
func() #此处执行的是bar函数
stop_time = time.time()
print('the func tun time is %s'%(stop_time-start_time))
def bar():
time.sleep(3)
print('in the bar.')
test1(bar) #将bar函数名传递到test1中
#执行结果
in the bar.
the func tun time is 3.0044002532958984
这种方式的作用可以满足装饰器中的第一个作用,就是可以不修改函数的源代码,并且为其添加功能;
那第二种高阶函数,返回值中包含函数名:
import time
def test2(func):
print(func)
return func
def bar():
time.sleep(3)
print('in the bar')
print(test2(bar)) #此处一共打印2次bar的内存地址,,第一次打印的是print(func),第二次打印的是return func,打印的是test2中return的bar函数的内存地址
bar = test2(bar)
# test2(bar)的结果就是bar,只不过函数的过程中有一个print,但是结果是bar
#第三次打印bar的内存地址(执行test2),就相当于将test2(bar)的return的值重新赋值给bar
bar() #执行bar函数
#执行结果
<function bar at 0x103a21730>
<function bar at 0x103a21730>
<function bar at 0x103a21730>
in the bar #bar()的执行结果
这里就会用到函数即“变量”的概念,
这种方式的作用可以满足装饰器中的第二个作用,可以不修改函数的调用方式。
③.嵌套函数
嵌套函数就是在函数内部在定义一层函数,作用是防止返回的函数被执行一次,在上面的高阶函数中看到当定义bar = test2(bar)时,函数test2会被执行一次;这肯定不是我们想要的,我们只是想要这个函数的内存地址,并且这个函数在被调用前不会被执行,因此需要使用嵌套函数;另外还有一个功能就是可以给被装饰的函数传递参数;
def foo():
print('in the foo.')
def bar(): #相当于局部变量的概念
print('in the bar')
# bar()
return bar
foo()
#执行结果
in the foo.
4、装饰器
核心概念:
高阶函数+嵌套函数 => 装饰器
def timer(func): #timer(test1) func=test1
def deco():
start_time = time.time()
func()
stop_time = time.time()
print('the func run time %s' %(stop_time-start_time))
return deco
@timer # 相当于test1 = timer(test1)
def test1():
time.sleep(3)
print('in the test1')
test1()
#执行结果
in the test1
the func run time 3.0033481121063232
python内存对装饰器有一个专门的方法,那就是在在被装饰函数的前一行加上'@装饰器名称';并且test1 = timer(test1),这种方法在test1有参数时就无法用了;
5、被装饰函数的参数与装饰器的参数
被装饰的函数带有参数
import time
def timer(func):
def bar():
start_time = time.time()
func()
stop_time = time.time()
print("this func run'time is %s" %(stop_time-start_time))
return bar
@timer
def test1(name,age):
print("this is %s,age is %d"%(name,age))
test1('obm',26)
test1('obm',26) = timer(test1)('obm',26)= bar('obm',26)
直接在装饰器中的bar()位置,添加参数即可,然后传递到func()中,参数最好使用参数组的方式来定义,方便重用。函数改写如下:
import time
def timer(func):
def bar(*args,**kwargs): #要定义参数组
start_time = time.time()
func(*args,**kwargs) #在调用被装饰的函数时,传递参数
stop_time = time.time()
print("this func run'time is %s" %(stop_time-start_time))
return bar
@timer
def test1(name,age):
print("this is %s,age is %d"%(name,age))
test1('obm',26)
装饰器自身带有参数
import time
user,passwd = 'alex','abc123'
def auth(auth_type):
def outer_wrapper(func):
def wrapper(*args,**kwargs):
if auth_type == 'local':
username = input('Username:').strip()
password = input('Passwrod:').strip()
if user == username and passwd == password:
print('\033[32;1mUser has passwd authentication\033[0m')
res = func(*args,**kwargs) #不定义变量的话,此处的返回值无法获取
print('----after authentication')
return res #此处返回的是home函数的返回值
else:
exit("\033[31m;1mInvalid username or password\033[0m")
elif auth_type == 'ldap':
print('搞毛线ldap,不会。。。。')
return wrapper
return outer_wrapper
def index():
print("welcome to index page")
@auth(auth_type='local') # home = auth(auth_type='local')(home)
def home():
print('welcome to home page')
return 'from home'
@auth(auth_type='ldap')
def bbs():
print('welcome to bbs page')
index()
home()
bbs()
参数传递的过程:
@auth(auth_type='local')
home = auth(auth_type='local')(home)
# 先执行一次auth函数,返回值是outer_wrapper
home = outer_wrapper(home)
@outer_wapper
# 这时返回的是wapper
# 因此在执行home时,就会执行wapper这个函数了;
也就是说当装饰器带有参数的话,则需要嵌套函数后在嵌套一层函数,一共是三层函数;
四、生成器
1、列表生成式
>>> [ i*2 for i in range(10)] #这就是一个列表生成式
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
2、生成器
在Python中,如果列表元素可以按照某种算法推算出来,就不必创建完整的list,从而节省大量的内存空间;这种一边循环一边计算的机制,称为生成器:generator。
生成器的作用是节省内存空间,只有在调用时才会生成相应的数据,(调用哪次生成哪次的数据)。
特性是只记录当前的位置
创建生成器
>>> l = [ i*2 for i in range(3)]
>>> l
[0, 2, 4]
>>> g = (i*2 for i in range(3))
>>> g
<generator object <genexpr> at 0x103d6f0a0>
>>>
只需将列表生成式的[]改为(),就可以创建一个生成器了;
生成器的方法next(),用来取出生成器的数据;
>>> g.__next__()
0
>>> g.__next__()
2
>>> next(g)
4
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
生成器取值的正确做法是使用for循环;这样就不用关心StopIteration的错误
>>> for i in g:
... print(i)
...
0
2
4
函数式生成器
生成器的另外一种生成方式,那就是使用函数;
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
def fib(max):
n,a,b = 0,0,1
while n < max:
print(b)
a,b = b,a+b
n = n + 1
#return 'done'
此处的赋值方法:
t = (b,a+b) #t是一个tuple
a = t[0]
b = t[1]
并且不用设置临时变量t;
fib(6)
1
1
2
3
5
8
要将fib函数改成生成器的话,只需要将print(b)改成yield b即可;
def fib(max):
n,a,b = 0,0,1
while n < max:
# print(b)
yield b
a,b = b,a+b
# t = (b,a+b)
# a = t[0]
# b = t[1]
n = n + 1
return done #生成器的返回值就是generator的StopIteration打印的消息
generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
捕获错误
捕获StopIteration错误,实际上就是返回生成器函数的return值;
g = fib(6)
while True:
try:
x = next(g)
print('g:',x)
except StopIteration as e:
print('Generator return value:',e.value)
break
#执行结果
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
还可通过yield实现在单线程的情况下实现并发运算的效果
import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield #yield保存当前位置,并退出
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))
def producer(name):
c = consumer('A')
c2 = consumer('B')
c.__next__()
c2.__next__()
print("老子开始准备做包子啦!")
for i in range(10):
time.sleep(1)
print("做了1个包子,分了两半!")
c.send(i) #send,不仅调用yield,并且将值传递给了yield,而next只会调用yield,不会给yield传值,
c2.send(i)
producer("alex")
生成器的send方法不仅调用yield,并且将值传递给了yield,而next只会调用yield,不会给yield传值,
协程,比线程还小的单位;寄生在线程里;
yield与send 配合就是协程;
五、迭代器
可以直接作用于for循环的对象统称为可迭代对象(可循环的对象),iterable。
Isinstance()判断一个对象是一个iterable的类型
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable) #数值是一个值,无法迭代
False
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
生成器肯定是一个迭代器;但是迭代器不一定是生成器。
List,dict,str等是iterable,可以使用iter()函数将iterable
变成iterator;
Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
因此list、dict、str等数据类型不是Iterator。

1.迭代器一定是迭代对象,迭代对象不一定是迭代器
2.生成器一定是迭代器,迭代器不一定是生成器
3.使用for...in...来遍历迭代对象是最常用的方式,或者使用next();
python2和3的一些区别
Python3.0 中的range(10) 就是一个迭代器;
Python2.0 中要使用xrange(10),才是一个迭代器,但是range(10)是一个list;
Python3.0 中 xreadline,才能使用file.next方法
Python2.7 中可以直接使用file.next
六、内置函数
https://docs.python.org/3/library/functions.html?highlight=built#all

abs() 返回绝对值
all() 如果iterable的所有元素都为真 (或者 iterable 为空), 则返回True 。非0即为真;
any() 如果iterable的任何元素为 true, 则返回True,如果 iterable 为空, 则返回False,ascii() 转化为可打印表示形式的字符串
a = ascii([1,2,'我的'])
print(type(a),[a])
<class 'str'> ["[1, 2, '\\u6211\\u7684']"]
bin() 将整数转换为前缀为 "0b" 的二进制字符串,10进制转换为二进制;
bool() 返回一个布尔值, 即True或False中的一个
bytearray() 返回一个新的字节数组,这个是可以修改的;但是只能赋值为ascii的编码数字;
byte() 返回一个新的 "字节" 对象
a = bytes('abc',encoding='utf-8') #字节与字符串转换
b = bytearray('abc',encoding='utf-8')
print(a.capitalize(),a)
print(b[0])
print(b[1])
print(b[2])
b[1] = 10 #必须赋值为ascii的编码数字
#执行结果
b'Abc' b'abc'
97
98
99
callable() 判断函数或者类的参数是否可调用,
chr() 返回表示其 Unicode 编码整数的字符的字符串,把数字的编码范围为字符,参数必须是数字;
ord() 与chr()相对应,参数必须是字符,范围的ascii的编码数字
compile 编译代码的过程,
complex 返回一个复数;
dir() 返回对象的所有方法;
divmod(a,b) 求余数
eval() 将字符串转换为字典,还可以处理逻辑运算
exec() 将字符串转换为代码并执行;
filter() 过滤,一般与lamdba结合使用
map()返回一个迭代器, 它将函数应用于iterable的每个项, 从而产生结果
flat() 返回浮点数
format() 标准化输出
frozenset() 不可变的集合
globals() 返回当前程序中的所有全局变量,以字典形式表示;
hex() 把数字转换成十六进制
iter()
locals() 打印局部变量
oct(x) 把数字转换成八进制
pow(x, y) 返回次方值
repr(对象) 返回一个包含对象的可打印表示形式的字符串
reversed(seq) 反转
Round(数字[,ndigits]) 返回小数点后的数字舍入为ndigits精度,
Sorted() 给字典排序,
Zip() 拉链,会将两个列表组合起来;也可以将组合起来的数据组成字典
title = "id,name,age,phone,dept,enroll_date"
line = "4,Mack Cao,40,1356145343,HR,2009-03-01"
dic = dict(zip(title.split(','),line.split(',')))
print(dic)
{'id': '4', 'name': 'Mack Cao', 'age': '40', 'phone': '1356145343', 'dept': 'HR', 'enroll_date': '2009-03-01'}
import() 导入模块时,使用字符串代替模块名;
__import__(“os”)
import os
七、文件序列化和反序列化
用于序列化的两个模块:json,pickle
json 用于字符串 和 python数据类型间进行转换;只能处理最简单的数据,字典、list等,因为json可以夸语言;
json.dumps(): 对数据进行编码。
json.loads(): 对数据进行解码。
json.dumps 用于将 Python 对象编码成 JSON 字符串。
json.loads 用于解码 JSON 数据。该函数返回 Python 字段的数据类型。
pickle.dump(数据, 文件名) # f1.write(pickle.dumps(数据))
Pickle 用于python特有的类型 和 python的数据类型间进行转换,只有python中能用,
Json模块提供了四个功能:dumps、dump、loads、load
dumps与dump的区别:
dump是将obj序列化为 JSON 格式的流到fp。
dumps是将obj序列化为 JSON 格式的 str 。
pickle模块提供了四个功能:dumps、dump、loads、load
序列化:
import json
info = {
'name':'alex',
'age':22
}
f = open('test.txt','w')
f.write(str(info)) #将字典直接转换为字符串,但是读出来的也是字符串,可以使用eval转换为字典
f.close()
f1 = open('test.txt','w')
#print(json.dumps(info))
f1.write(json.dumps(info))
f1.close()
import pickle
f1 = open('test.txt','wb')
#print(pickle.dumps(info))
f1.write(pickle.dumps(info))
f1.close()
pickle.dump(info,f1) # f1.write(pickle.dumps(info))
f.close()
反序列化:
import json
f = open('test.txt','r')
data = eval(f.read())
f.close()
print(data['age'])
f = open('test.txt','r')
data = json.loads(f.read())
print(data['age'])
import pickle
f = open('test.txt','rb')
data = pickle.loads(f.read())
print(data)
data = pickle.load(f) #data = pickle.loads(f.read())
要注意的是写程序时,一个文件只能dump一次,load一次;
八、软件目录结构规范
假设你的项目名为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的内容
这个是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。
它需要说明以下几个事项:
软件定位,软件的基本功能。
运行代码的方法: 安装环境、启动命令等。
简要的使用说明。
代码目录结构说明,更详细点可以说明软件的基本原理。
常见问题说明。
九、主函数
if “__name__” == "__main__":
在cmd 中直接运行.py文件,则‘name’的值是'main';
而在import 一个.py文件后,‘name’的值就不是'main'了;
从而用if __name__ == '__main__'来判断是否是在直接运行该.py文件;
http://blog.konghy.cn/2017/04/24/python-entry-program/
十、多级目录调用python模块
一般情况下,首先寻找当前目录是否有符合的模块名称,然后查找python的环境变量(sys.path);
而多级目录调用python模块时,例如:
ATM
├── bin
└── ATM.py
├── config
└── default.conf
├── core
│ └── main.py
├── docs
└── README
若要在bin下的ATM.py文件调用main.py模块,首先要将core这个文件夹给添加到python的系统变量中。
python自带的有一个方法:file,能够打印出当前文件的路径;
os.path.abspath():可以得出当前文件的绝对路径
os.path.dirname():可以得出路径的目录名
os.path.basename():可以得出路径中的文件名
print(__file__) #当前文件的路径
import sys,os
print(os.path.abspath(__file__)) #当前文件的绝对路径
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #当前文件的父目录的父目录
print(sys.path) #python的环境变量
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0,BASE_DIR) #将当前的目录的父目录的父目录添加到python的环境变量中,这样就可以
#执行结果
/Users/chenzhuanglan/OneDrive/learning/python/scripts/LearnPython/day4/多级目录调用python程序.py
/Users/chenzhuanglan/OneDrive/learning/python/scripts/LearnPython/day4/多级目录调用python程序.py
/Users/chenzhuanglan/OneDrive/learning/python/scripts/LearnPython
['/Users/chenzhuanglan/OneDrive/learning/python/scripts/LearnPython/day4', '/Users/chenzhuanglan/OneDrive/learning/python/scripts/LearnPython', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Applications/PyCharm.app/Contents/helpers/pycharm_matplotlib_backend']


浙公网安备 33010602011771号