python整理基础知识点
入门简介:
开发语言:
高级语言:Python、Java、PHP、 C#、Go、ruby、C++:写英文,软件转给C语言再化为0101位交给操作系统
低级语言:C、汇编
机器码和字节码:
机器码:0101系统能识别的,低级语言转换字节码成机器码
字节码:高级语言内部转换的语言
语言之间的对比:
高级语言开发效率高,都要用到低级语言
低级语言执行效率高,涉及内存控制
php适合网页,python和java网页后台,但python开发效率高,执行效率低了点
编译型与解释型:
编译型,要求必须提前将所有源代码一次性转换成二进制指令
如C语言、C++、Golang、Pascal(Delphi)、汇编等
解释型语言,运行时先编译需要运行的部分再解释(脚本语言)
如JavaScript、VBScript、Perl、Python、Ruby、MATLAB
例外:
JAVA语言是一种编译型-解释型语言,同时具备编译特性和解释特性。
作为编译型语言,JAVA程序要被统一编译成字节码文件——文件后缀是class。
而java虚拟机的翻译过程则是解释性的。java字节码文件首先被加载到计算机内存中,然后读出一条指令,翻译一条指令,执行一条指令,该过程被称为java语言的解释执行,是由java虚拟机完成的。
C#程序在第一次运行的时候,会依赖其.NET Frameworker平台,编译成IL中间码),
然后由JIT compiler翻译成本地的机器码执行。从第二次在运行相同的程序,则不需要再执行以上编译和翻译过程,而是直接运行第一次翻译成的机器码。
强类型与弱类型:
强类型:
一旦某一个变量被定义类型,如果不经强制转换,那么它永远就是该数据类型。
如python3的类型定义,golang、java
弱类型:
偏向于容忍隐式类型转换
比如python2,python3不加类型限制,VB、PHP、JavaScript
动态语言和静态语言:
动态类型语言:
是指在运行期间才去做数据类型检查的语言。如python
静态类型语言:
数据类型检查发生在编译阶段,也就是说在写程序时要声明变量的数据类型。C/C++、C#、Java、golang
Python种类:
通常的python是cpython,直接转c
也有基于java和c#开发的Jpython和C#python,转换位对应语言,再转为c语言,但与java等交互性好
JPython
IronPython
JavaScriptPython
RubyPython
....
pypy python开发的python,先内部执行一下拿到字节码对应的机器码,以后再执行机器码即可
运算符:
一元运算符:
-(__neg__)
一元取负算术运算符。如果x 是-2,那么-x == 2。
+(__pos__)
一元取正算术运算符。通常,x == +x,例外是decimal精度和collections.Counter只返回>0的计数器
~(__invert__)
对整数按位取反,定义为~x == -(x+1)。如果x 是2,那么~x == -3。
中序运算符:
+(__add__(self, other))
如果a+b失败,返回NotImplemented,会检测b有没有__radd__ 方法,最后才返回TypeError
a["key"]+=1触发的是__getitem__方法
*(__mul__(self, scalar))
- (__sub__)
__isub__就地减
//(__floordiv__)
整除
% (__mod__)
取模
divmod()(__divmod__)
返回由整除的商和模数组成的元组
**,pow()(__pow__)
取幂
@(__matmul__)
矩阵乘法
位运算符:
&(__and__) 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0
|(__or__) 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。
^(__xor__) 按位异或运算符:当两对应的二进位相异时,结果为1。和前后顺序无关。
~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1 。~x 类似于 -x-1
<<(__lshift__) 左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的数字指定了移动的位数,高位丢弃,低位补0。
按位左移,__lshift__ __rlshift__ __ilshift__
>>(__rshift__) 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,>> 右边的数字指定了移动的位数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)
按位右移,__rshift__ __rrshift__ __irshift__
比较运算符:
== a.__eq__(b) b.__eq__(a) 如果反向调用失败后,返回id(a) == id(b)
!= a.__ne__(b) b.__ne__(a) 返回not (a == b)
> a.__gt__(b) b.__lt__(a) 抛出TypeError
< a.__lt__(b) b.__gt__(a) 抛出TypeError
>= a.__ge__(b) b.__le__(a) 抛出TypeError
<= a.__le__(b) b.__ge__(a) 抛出TypeError
增量赋值运算符:
如果一个类没有实现就地运算符,增量赋值运算符只是语法糖:a += b 的作用与a = a + b 完全一样,对于可变类型和不可变类型始终创建新对象
如果实现了就地运算符方法,会就地修改左操作数,而不会创建新对象作为结果。
# 不可变类型,一定不能实现就地特殊方法
# 此外,如+=与+等相比,要求右边是任何可迭代对象即可,无需同一类型。
数据类型:
数字:
整数:
概述:
python2:int long
python3: int
方法:
int():转为数字
int(,base=16):以16进制的形式转为10进制
big_lenght:二进制的有效位数
rount(value, ndigits) python3不一定四舍五入,中间数会取最近偶数
bin():二进制
oct():八进制
hex():16进制
int.from_bytes() 字节转int
xx.to_bytes(nbytes,'') int转字节,第二个参数可选。big\little,字节的低位高位排列方式,先高还是先低
进制转换:
bin(x):将整数转为二进制
oct(x):将整数转为八进制
int([number | string[, base]])
hex(x):将整数转为16进制
ord():将字母转为ASCII码对应的值
进制转换:
N进制转十进制:
n1*8**0 + n2*8**1+n3*8**2+... = 十进制数
十进制转N进制:
十进制数除以n,得到商和余数,一直到商不能整除为止,商+余数
浮点数:
普通浮点数:
float
正无穷:float('inf') # math.isinf(a)
负无穷:float('-inf')
非数字:float('nan') # math.isnan(c)可判断,有些操作时未定义的并会返回一个NaN结果,NaN 值会在所有操作中传播,而不会产生异常
# NaN 值的一个特别的地方时它们之间的比较操作总是返回False
精确浮点数:
decimal.Decimal('6.3')
复数:
定义:
complex(real, imag)或字面量语法3-5j
相关模块:
常见的数学运算外,复数函数在cmath模块中,如cmath.sin(),cmath.sqrt()
分数:
from fractions import Fraction
Fraction(5, 4)或Fraction(*x.as_integer_ratio()) # as_integer_ratio返回两个int元组
Fraction.numerator 分子
Fraction.denominator 分母
Fraction.limit_denominator(8) 极限分母,指定
字符串str:
概述:
字符串一旦创建不可修改,修改就会重新创建字符串。原因是内存连续,修改会存在字节位数不同的问题
常用方法:
capitalize():首字母大写
casefold():小写,除英文外还支持更多的语言对应
lower():小写,islower()
upper():大写,isupper()
center(width,fillchar=None):一共多长,中间是字符串,其余填充指定的一个符号
ljust(width):字符串开头,后面填充指定符号
rjust():字符串结尾,开头填充
count(sub,start=None,end=None):计算出现的次数,能指定开始和结束位置
encode()
decode()
endswith(sub):以什么结尾,返回布尔值,也可传元组
startswith():开头
expandtabs(8):以括号中数字为一组长度,遇到\t就空白补充,'asdd\tasd\tas'
find():找到子序列的开始位置,未找到-1
rfind(text,st,end):返回最后找到的位置,未找到-1
format():格式化,将{name}替换为指定值(name='ss'),占位符是数字{0}时顺序替换(v1,v2)
format_map():传字典,v="i am {name},{abc}" v.format_map({"name":'ss',"abc":"sa"})
index():找到子序列的开始位置,未找到报错
isalnum():是否只包含字母和数字
isalpha():是否字母,汉字
isdecimal():是否十进制
isdigit():不同格式的数字,②,不支持中文
isnumeric():支持中文
isidentifier():返回bool,是否是标识符,字母、数字、下划线组成,不以数字开头
isprintable():是否不存在不可显示的字符\n \t
isspace():是否全部是空格
title():每个单词首字母大写,衍生 istitle()
join():示例:" ".join('abc')返回"a b c"。join前是填充符,后面是要被填充的字符串,每一个元素之间用填充符并接
strip():移除两边空格\n和\t,可以指定移除字符或字符串(一个个匹配,有则移除)。衍生:lstrip() rstrip()
maketrans('',''):指定替换关系
translate(m):按照括号内的替换关系进行替换,m是dict,value对应None时删除
partition():按照括号内的元素,分割一次,元组,三个元素,第二个是本身
rpartition():从右边开始分割
split('',num):分割,能传最多分割次数,没有自身,返回列表,# re.split("",str)能指定多个正则模式
rsplit()
splitlines(bool):根据换行进行分割,True时保留\n
swapacse():大小写切换
replace('','',num):替换,能传次数
索引:
test[0:1] 不包括1
-1:最后位置
len()长度:
python3中:字符长度
python2中:字节长度
字符串格式化:
format函数:
概述:
使用的表示法叫格式规范微语言
适用于字符串、数字、其他值
语法:
format(my_obj, format_spec)函数
其中format_spec是格式说明符,在str.format()方法中是{}里代换字段中冒号后面的部分,前面部分是字段名称
底层实现:
把各个类型的格式化方式委托给相应的.__format__(format_spec)方法,如果没有定义__format__ 方法,从object 继承的方法会返回str(my_object)
str.format():
[[fill]align][sign][#][0][width][,][.precision][type]
如:{0:*>3}
第一个0表示分组,:前跟名字,后跟格式,*是填充符,>向右对齐,3表示填充宽度
此外,0.x:*>3表示用第1 个参数的x 属性
fill 【可选】空白处填充的字符
align 【可选】对齐方式(需配合width使用)
<,内容左对齐
>,内容右对齐(默认)
=,内容右对齐,将符号放置在填充字符的左侧,且只对数字类型有效。 即使:符号+填充物+数字
^,内容居中
sign 【可选】有无符号数字
+,正号加正,负号加负;
-,正号不变,负号加负;
空格 ,正号空格,负号加负;
# 【可选】对于二进制、八进制、十六进制,如果加上#,会显示 0b/0o/0x,否则不显示
width 【可选】格式化位所占宽度
, 【可选】为数字添加分隔符,如:1,000,000
.precision 【可选】小数位保留精度
type 【可选】格式化类型
传入” 字符串类型 “的参数
s,格式化字符串类型数据
空白,未指定类型,则默认是None,同s
传入“ 整数类型 ”的参数
b,将10进制整数自动转换成2进制表示然后格式化
c,将10进制整数自动转换为其对应的unicode字符
d,十进制整数
o,将10进制整数自动转换成8进制表示然后格式化;
x,将10进制整数自动转换成16进制表示然后格式化(小写x)
X,将10进制整数自动转换成16进制表示然后格式化(大写X)
传入“ 浮点型或小数类型 ”的参数
e, 转换为科学计数法(小写e)表示,然后格式化;
E, 转换为科学计数法(大写E)表示,然后格式化;
f , 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
F, 转换为浮点型(默认小数点后保留6位)表示,然后格式化;
g, 自动在e和f中切换
G, 自动在E和F中切换
%,显示百分比(默认显示小数点后6位)
格式转化:
一个对象本身不是str,ascii,repr格式,可以使用!s、!a、!r,将其转成str,ascii,repr
如{!a:*>3}
百分号方式:
%[(name)][flags][width].[precision]typecode
(name) 可选,用于选择指定的key
flags 可选,可供选择的值有:
+ 右对齐;正数前加正好,负数前加负号;
- 左对齐;正数前无符号,负数前加负号;
空格 右对齐;正数前加空格,负数前加负号;
0 右对齐;正数前无符号,负数前加负号;用0填充空白处
width 可选,占有宽度
.precision 可选,小数点后保留的位数
typecode 必选
s,获取传入对象的__str__方法的返回值,并将其格式化到指定位置
r,获取传入对象的__repr__方法的返回值,并将其格式化到指定位置
c,整数:将数字转换成其unicode对应的值,10进制范围为 0 <= i <= 1114111(py27则只支持0-255);字符:将字符添加到指定位置
o,将整数转换成 八 进制表示,并将其格式化到指定位置
x,将整数转换成十六进制表示,并将其格式化到指定位置
d,将整数、浮点数转换成 十 进制表示,并将其格式化到指定位置
e,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(小写e)
E,将整数、浮点数转换成科学计数法,并将其格式化到指定位置(大写E)
f, 将整数、浮点数转换成浮点数表示,并将其格式化到指定位置(默认保留小数点后6位)
F,同上
g,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过6位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是e;)
G,自动调整将整数、浮点数转换成 浮点型或科学计数法表示(超过6位数用科学计数法),并将其格式化到指定位置(如果是科学计数则是E;)
%,当字符串中存在格式化标志时,需要用 %%表示一个百分号
布尔值bool:
概述:
True False
注:在JavaScript中小写 true false
转换函数:
bool()
False的几种情况:
空字符串"" None () [] {} 0
注:None只是False的情况之一,其他情况不是None
列表list:
元素类型:
列表元素可以是数字,字符串,列表,布尔值,所有都能放
切片:
切片取到的是列表,范围索引可以超出范围,[][-1:] [][21:]
修改:
列表可以修改,内存不连续,链表,存有下一块的地址
li[1:3]=[]
删除:
del li[0]
转换:
list('asd') 默认每一个字符是一个元素
"".join(li),li必须全是字符串
方法:
append():追加元素到右边 li.append(2)
clear():清空,原列表变成[] li.clear() python3特有
copy():浅拷贝,返回一个列表,需要值接收一下 v=li.copy()
count():计算出现个数 v = li.count(11)
extend():循环括号内每个元素追加到列表,可迭代对象,如字符串、列表、元组 li.extend([22,21]) # extend与append的区别就是extend可以同时添加多个元素
index():找到第一个位置,未找到报错
insert():指定位置插入,li.insert(0,11),对append的补充
pop():删除最后一个,可以指定位置,返回删除值
remove():根据值删除列表元素,第一个
reverse()\reversed():反转
sort()\sorted(li,key=部分值\函数返回值):排序,reverse=True时从大到小排
in li :是否是li的元素
列表与其他数据类型的比较:
数组array
数组在背后存的并不是float对象,而是数字的机器翻译,也就是字节表述。效率高得多
先进先出的序列:
deque(双端队列)的速度应该会更快。deque(range(10), maxlen=10)
包含操作:
set(集合)会更合适,set 专为检查元素是否存在做过优化。
切片对象:
slice(start, stop, step)
如
a = [1,2]
b = slice(0,1)
print a[b]
切片原理:
S.indices(len) -> (start, stop, stride)
indices会根据len,审核s表示的切片的起始(start)和结尾(stop)索引,以及步幅(stride),以避免IndexError异常
range函数:
python2中会打印出来,要用xrange()
python3中只创建内存对象,for循环才会创建
range(0,100,5) 还能指定步长,也支持负数range(100,0,-1)
list列表的增量赋值:
+= 背后的特殊方法是__iadd__(用于“就地加法”)。但是如果一个类没有实现这个方法的话,Python 会退一步调用__add__。
区别在于:__iadd__不会先创建一个新对象然后再赋值,效率高
# 对于不可变对序列的增量赋值,先复制原元素到新位置,再对新元素追加。效率低
# str例外,cpython做了优化,留出额外可扩展空间
边界谜题
t = (1, 2, [30, 40])
t[2] += [50, 60]
元组为不可变对象,但其中元素可以为list等可变对象,在对元组的list元素进行增量赋值时,
过程:
1. 先复制list到栈的顶端
2. 该list进行增量操作,然后赋值给自身 # 对于不可变序列,如str,元组等,也能增量操作
3. 新值赋值给元组该位置,失败,因为元组不可变 # 最终都是这个步骤失败
列表推导式:
简单用法
[x for x in list]
笛卡尔积
[(color, size) for color in colors for size in sizes] 每个color遍历所有size
元组tuple:
特征:
元素不可被修改、删除或者增加,但元素的元素比如列表元素可以操作
有序
写元组时,推荐最后加逗号,区分方法不能加逗号
索引[],切片(返回元组)
转换:
tuple():可以转换字符串、列表为元组
list():元组转换为列表
"".join():转为字符串,要求元素全是字符串
方法:
count()
index()
拆包:
嵌套元组,for x,y in 可以拆包,忽略多余的元素可以用*,不需要的元素可以用_占位符处理
此外,函数调用时参数中的*可以将可迭代对象拆包,func(a,b,*[c,d],**{"e":1,"g":2})
比较:
两个元组比较时,从头到后一个个比较元素
字典dict:
特征:
键值对,无序
字典的key:不能是列表和字典,因为字典保存用到哈希,键要转为数字
temp = dict时,temp指向dict的内存地址,修改temp的值,dict的值也随之改变
推导式:
{v: k for k, v in mcase.items()} # 更快
dict((k,v) for k,v in dic.items() if k in li)
删除:
del pop('k1')
方法:
keys():键,默认,返回展现键集合的键视图对象,支持集合操作
values():值,不支持支持集合操作
items():键,值,返回视图,支持集合操作
get():没有会返回None,或者传入默认值
pop('k1'):删除,返回删除的value,能传入默认值
popitem():随机删除,返回键值对元组 # 有序dict会默认删除最近插入的元素 last=True
update():没有就设置,有就更新。传字典,或者关键字参数(k1=11,k3=22)
clear()
copy()
dict.formkeys():静态方法,类+方法(),第一个参数是key,如果可迭代循环拿,第二个是值
# 不传value时{1: None, 2: None, 3: None} dict.fromkeys(d,'xiaodeng')
setdefault('k1','v1'):没有就设置,有则不设置,返回value
in 判断key是否存在
# eval能将字符串的字典转为dict
# json.loads将字符串字典转字典时,属性或者属性值,都必须是双引号括起来的。需注意python2中的长整型单位L不能转换,报错
# json.loads(ret.group(0).replace("'",'"').replace("L",""))
字典与JSON格式的异同:
1. python dict 字符串用单引号,json强制规定双引号。
2. json key name 必须是字符串, python是hashable,如元组也可以
3. json: true false null 对应python的True False None
4. python dict value里可以嵌套tuple, json里只有array,json.dumps({1:(1,2)}) 的结果是{"1":[1,2]}
5. python {“me”: “我”} 是合法的 json 必须是 {“me”: “\u6211”}
可散列的数据类型:
一个对象是可散列的,那么在它的生命周期中它的hash 值是不变的。
可散列的对象需要2个方法:__hash__()方法和__eq__()方法。两个可散列的对象相等,那么它们的散列值相等。
1. 原子不可变数据类型(str、bytes 和数值类型)
2. frozenset
3. 所有元素都是可散列类型的元组
4. 所有由用户自定义的对象默认都是可散列的,因为它们的散列值由id()来获取
如果改写了__hash__()方法或__eq__()方法,散列值获取或比较的不是id时,这个对象就不是可散列对象了。
解决:
读取的属性改为类的property,不能修改(实例的散列值绝不应该变化,所以通过property设置只读特性)
改写,使得__eq__()比较后相等的两个对象,__hash__()拿到的值也相同即可 # 默认比较id()
# 这两个方法也是list、set、dict不可hash的原因,值==却hash的是id值
对象多个属性的散列值,最好使用位运算符异或(^)混合各分量的散列值
hash(self.x) ^ hash(self.y)
集合set:
概述:
去重
无序,次序取决于被添加到集合里的次序,往集合里添加元素,可能会改变集合里已有元素的次序。不能索引(因为无序),可以循环迭代取(类似字典)
存放不可变类型(可哈希):数字、字符串、元组
set和frozenset的实现也依赖散列表,但在它们的散列表里存放的只有元素的引用
定义:
s = set() # 空集合只能这种方式
s = {1,2,3} # 速度更快,不用查set方法,然后新建列表,而是专门的BUILD_SET字节码来创建
s = set((1,2)) # 只能传一个参数,内部会遍历这个参数,生成一个个元素
方法:
add()
clear()
copy()
pop()随机删除
remove(x) 指定删除,不存在会报错
discard(x) 如果s 里有e 这个元素的话,把它移除,不存在不会报错
update:更新没有的a.update(b),与并集(无更新)有区别
in 对可迭代对象都能使用,判断是否是其中的元素,列表需扫描整个list,字典的keys()在python3返回视图,和集合一样查找很快
intersection:交集 a.intersection(b) 相当于a&b,# 区别是intersection的参数只需可迭代,转为集合,而符号则必须集合
intersection_update
union:并集 a.union(b) 相当于a|b
update
difference:差集 a.difference(b) 相当于a-b
difference_update:求差集并更新 a.difference_update(b) 相当于a = a-b
symmetric_difference:交叉并集 a.symmetric_difference(b) 相当于a^b,反向交集
symmetric_difference_update
isdisjoint:没有交集时返回true a.isdisjoint(b)
issubset:a<=b时返回true,是否子集
issuperset:是否父集
frozenset:
不可变集合,可散列
优点:
与list比较,添加和删除元素的速度更快,in的操作更快
有序集合:
利用OrderedDict
数组array:
定义:
array.array('d',[1,2]) # b表示有符号的字符
优点:
如果想要只包含数字的列表,那么array.array 比list 更高效。
方法:
支持所有跟可变序列有关的操作,包括.pop、.insert 和.extend。
另外,数组还提供从文件读取和存入文件的更快的方法,如.frombytes(f,n)最多n个 和.tofile()。
# array.fromfile读取1000万浮点数非常快,使用内置的float 方法把每一行文字转换成浮点数。tofile比以每行一个浮点数的方式快7倍。
# 与之媲美的是pickle模块,可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。
排序:
不支持list.sort() 这种就地排序方法,要给数组排序的话,得用sorted 函数新建一个数组
a = array.array(a.typecode, sorted(a))
想要在不打乱次序的情况下为数组添加新的元素
bisect.insort
collections模块:
namedtuple具名元组:
定义:
collections.namedtuple(typename, field_names, verbose=False, rename=False),返回类
typename是要创建的子类类名
field_names可以是['name', 'age', 'id'],或者'name age id'
使用:
通过 元组对象.field_name 来获取元组的一个值,与元组类型可交换,支持所有的普通元组操作,比如索引和拆包
# 小量数据不需更新的部分场景可以取代字典,大量数据需更新可以用__slots__ 方法的类
方法:
_fields:包含这个类所有字段名称的元组
_make():接受一个可迭代对象来生成这个类的一个实例, 它的作用跟City(*delhi_data) 是一样的。
_asdict():把具名元组以collections.OrderedDict 的形式返回
_replace(xx=1):创建新命名元组,用新值取代
deque双边队列:
方法:
1.在 list 的基础上增加了移动、旋转和增删等。如append/appendleft/extendleft/popleft等
2. maxlen可选,如果队列已满,会挤掉添加方向的反方向的元素
# 用于保留最后N个元素
优点:
1. 列表通过append和pop(0)实现的队列,在删除列表的第一个元素(抑或是在第一个元素之前添加一个元素)之类的操作是很耗时的,
因为这些操作会牵扯到移动列表里的所有元素。
2. append 和popleft 都是原子操作,多线程安全
缺点:
只对在头尾的操作进行了优化。从队列中间删除元素的操作会慢一些
defaultdict:
处理找不到的键的一个选择,需要给它配置一个为找不到的键创造默认值的方法
如 d = defaultdict(list),list会在初始化被赋值给属性default_factory,在找不到key时通过__missing__调用default_factory
也可自定义类,这样初始值就是这个类的实例
# 所有的映射类型在处理找不到的键的时候,都会牵扯到__missing__ 方法
OrderedDict:
内部维护着一个根据键插入顺序排序的双向链表,大小是一个普通字典的两倍
ChainMap:
容纳数个不同的映射对象,然后在进行键查找操作的时候,这些对象会被当作一个整体被逐个查找
pylookup = ChainMap(locals(), globals(), vars(builtins))
# 出现重复键,那么第一次出现的映射值会被返回
# 对于字典的更新或删除操作总是影响的是列表中第一个字典
Counter:
统计初始化给出的可迭代对象(如果为dict时,返回的是普通字典)中,每个key出现的次数,返回字典映射类型
方法:
most_common(n)按照次序返回映射里最常见的n 个键和它们的计数
UserDict:
把标准dict用纯Python又实现了一遍。
1.用于自定义映射类型,比普通的dict 为基类要来得方便:后者有时会在某些方法的实现上走一些捷径,导致我们不得不在它的子类中重写这些方法
2.UserDict有一个叫作data的属性,是dict的实例,这个属性实际上是UserDict最终存储数据的地方。
因为不是直接调用本身,能在实现__setitem__的时候避免不必要的递归,也可以让__contains__里的代码更简洁。
泛映射类型:
概述:
collections.abc 模块中有Mapping 和MutableMapping 这两个抽象基类,它们的作用是为dict和其他类似的类型定义形式接口
非抽象映射类型一般不会直接继承这些抽象基类,它们会直接对dict 或是collections.User.Dict 进行扩展。
作用:
1.这些抽象基类的主要作用是作为形式化的文档,它们定义了构建一个映射类型所需要的最基本的接口。
2.它们还可以跟isinstance一起被用来判定某个数据是不是广义上的映射类型 isinstance(my_dict, abc.Mapping)
实用被继承方法:
1. MutableMapping.update
这个update方法直接在自定义类可用,还可以在__init__方法里调用,使得构造方法可以利用各种传参(其他映射类型,元组对,键值参数)
来新建实例,这个方法背后是self[key] = value,通过__setitem__ 方法。
2. Mapping.get
普通dict的get方法不会触发__getitem__,而这个则会try self[key]来触发
不可变映射类型MappingProxyType:
返回一个只读的动态映射视图,d_proxy = MappingProxyType(my_dict)
bytes和bytearray对象:
组成:
各个元素是介于0~255(含)之间的整数,所以二进制序列其实是整数序列 # 而 str对象的单个元素是单个字符
各个字节的值可能会使用下列三种不同的方式显示:
1. 可打印的ASCII 范围内的字节(从空格到~),使用ASCII 字符本身。
2. 制表符、换行符、回车符和\ 对应的字节,使用转义序列\t、\n、\r 和\\。
3. 其他字节的值,使用十六进制转义序列(例如,\x00 是空字节)。
切片:
切片始终是同一类型的二进制序列 # 一个例外:s[0] == s[:1] 只对str这个序列类型成立
bytearray 对象的切片还是bytearray 对象。
字面量句法:
byte的字面量句法:b'caf\xc3\xa9'
bytearray没有字面量句法,而是以bytearray() 和字节序列字面量参数的形式显示。
方法:
除了格式化方法(format 和format_map)和几个处理Unicode数据的方法(包括casefold、isdecimal、isidentifier、isnumeric、isprintable 和encode)之外
str类型的其他方法都支持,这意味着,可以使用熟悉的字符串方法处理二进制序列
如:
endswith、replace、strip、translate、upper等
bytearray转str:
bytearray.decode() # str()只是bytearray的字面量表示,如bytearray(b'{"ref_id": 1221796, "event_type": 2}'),和str(byte)一个道理,没用
构造方法init:
一个str 对象和一个encoding 关键字参数 bytes(str,encoding='utf-8') # bytearray类似
一个可迭代对象,提供0~255 之间的数值 bytes([0-255的数值]) # bytearray类似
一个整数,使用空字节创建对应长度的二进制序列
一个实现了缓冲协议的对象 bytes(实现缓冲协议的对象,如bytes\bytearray\memoryview\array) 复制到新建的二进制序列中
# memoryview有点类似list的引用,修改memoryview会导致原缓冲类对象改变,共享内存
# 直接的复制bytes 或bytearray,会始终复制源对象中的字节序列
memoryview内存视图:
访问其他二进制序列、打包的数组和缓冲中的数据切片,而无需复制字节序列,共享内存
如处理图像,使用内存中的文件内容创建一个memoryview 对象,不会复制字节序列
场景:
在PIL图像、sqllite数据库、numpy array等数据结构之间不需第一层复制而共享内存,对于大数据集合很重要
# 能够对大文件创建memory-map,切片一小部分,基于numpy进行计算
序列类型:
容器序列
list、tuple 和collections.deque 这些序列能存放不同类型的数据。
存放的是它们所包含的任意类型的对象的引用
扁平序列
str、bytes、bytearray、memoryview 和array.array,这类序列只能容纳一种类型。
存放的是值而不是引用
可变序列
list、bytearray、array.array、collections.deque 和memoryview。
不可变序列
tuple、str 和bytes。
流程控制:
三元表达式:
a = 表达式1 if 条件 else 表达式2
示例:
a = 1+2 if 1>2 else 1,结果a=1
IF判断:
条件判断
用法:
if xxx:
elif xxx:
else:
FOR循环:
遍历迭代器
for 元素 in 迭代器:
# do xxxx
支持break、continue、pass
WHILE循环:
循环
while 判断条件(condition):
执行语句(statements)……
变量:
概述:
不用声明,直接定义
引用类型与值类型:
值类型:
概述:
数值,字符串,元组本身是值类型,本身不允许被修改。
变量保存的是值本身。
元组的相对不可变性:
概述:
元组与多数Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。
元组的不可变性其实是指tuple数据结构的物理内容(即保存的元素引用)不可变,与引用的对象无关。
示例:
如t1 = (1, 2, [30, 40]),可以进行t1[-1].append(5)的操作。但是不能进行t1[-1]=1这样的修改元素引用的操作。
其他类型:
仅对元组有这个特性,
而str、bytes 和array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。
引用类型:
概述:
列表,集合,字典是引用类型。
变量保存的是这些类型的内存地址。分配在堆上。
is与==的区别:
is 运算符比较两个对象的标识,id() 函数返回对象标识的整数表示。
is 运算符比== 速度快,因为它不能重载,所以Python 不用寻找并调用特殊方法,而是直接比较两个整数ID
而a == b 是语法糖,等同于a.__eq__(b)。继承自object 的__eq__方法比较两个对象的ID,结果与is 一样。
但是多数内置类型使用更有意义的方式覆盖了__eq__ 方法,会考虑对象属性的值。这种情况下与is不一样
深浅复制:
浅复制:
l2=list(l1)或l2 = l1[:]或copy()
复制了最外层容器,副本中的元素是源容器中元素的引用,对于类实例,内部属性list只是复制引用
如果没修改引用,如对list +=,append等,用的还是同一个list # 如果是可变对象,会就地修改。
如果修改了引用,如对不可变类型进行+=,那么引用就变为新引用 # 对于增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象;
l1 = [3, [66, 55, 44], (7, 8, 9)]
l2 = list(l1)
l2[2] += (10, 11)
深复制:
deepcopy 和copy 函数
遇到循环引用时,deepcopy 函数会记住已经复制的对象,因此能优雅地处理循环引用
a = [1,2]
b = [a,3]
a.append(b)
作用域:
概述:
Python有四类作用域(Scope)
分类:
局部(Local)作用域)
封闭(Enclosing)作用域。闭包。
全局(Global)作用域
内置(Built-in)作用域
局部变量:
概述:
在函数内声明的变量
命名规则:
局部变量名小写
作用域:
函数结束后回收。
只能在函数内使用。
封闭变量:
概述:
闭包引用的变量
nonlocal关键字:
声明变量为闭包变量。会尝试不断回溯外层,直到global作用域。如果仍然没有该变量,报错。
全局变量:
概述:
定义在函数之外的变量。
命名规则:
全局变量名大写
声明:
一般函数外声明
修改:
函数外可以直接修改
内书内需要先声明global,再进行修改。否则会创建同名的局部变量
使用:
直接可以使用
global关键字:
在函数内使用,显式声明这个是全局变量。
约束:
如果先创建局部变量,又声明了global,会报错,不知道找谁
如果未声明global,先使用局部变量后,再进行赋值修改,报错,因为有赋值行为,python编译时判断为局部变量
而JavaScript不声明局部变量var,局部没有则会获取全局变量
作用域:
可以在声明后的任何语句使用
del和垃圾回收:
del 语句删除名称,而不是对象
回收的情况:
del 命令可能会导致对象被当作垃圾回收,但是仅当删除的变量保存的是对象的最后一个引用,或者无法得到对象时。
重新绑定也可能会导致对象的引用数量归零,导致对象被销毁。
__del__:
即将销毁实例时,Python 解释器会调用__del__ 方法,给实例最后的机会,释放外部资源。随后销毁对象
垃圾回收:
主要算法是引用计数
每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁: # 赋予新值,重新绑定
CPython 会在对象上调用__del__方法(如果定义了),然后释放分配给对象的内存。
CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组
如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取,从而销毁
Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用__del__ 方法。
weakref.finalize(s1,func)
当s1绑定的对象销毁时,执行func,注意func不能是s1的对象的绑定方法,否则有一个指向对象的引用
.alive查看对象是否被销毁
原理:
weakref.finalize监控的参数绑定的对象使用的是弱引用,不会影响销毁
弱引用:
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。
弱引用不会妨碍所指对象被当作垃圾回收。
弱引用就是一个对象指针,它不会增加它的引用计数。
应用:
弱引用在缓存应用中很有用,因为我们不想仅因为被缓存引用着而始终保存缓存对象。
使用:
import weakref
a = {1,2}
wref = weakref.ref(a_set)
wref() # 如果存在,会返回被引用的对象,注意控制台会话输出结果会被绑定到变量_
a = {2} # 强引用减少
wref() # 不存在了,返回None
weakref集合
WeakValueDictionary
一种可变映射,里面的value是对象的弱引用。
被引用的对象在程序中的其他地方被当作垃圾回收后,对应的键会自动从WeakValueDictionary 中删除。
WeakKeyDictionary:
key是弱引用
为应用中其他部分拥有的对象附加数据,这样就无需为对象添加属性。这对覆盖属性访问权限的对象尤其有用。
WeakSet
保存元素弱引用的集合类。元素没有强引用时,集合会把它删除
局限:
1. 基本的list和dict实例不能作为弱引用对象,但子类可以
函数:
概述:
1.代码重用。2.保持一致性。3.可扩展性。
一等对象
满足了条件:
1.在运行时创建
2.能赋值给变量或数据结构中的元素
3.能作为参数传给函数,如map
4.能作为函数的返回结果
变量作用域:
对于一个变量,内部作用域先声明就会覆盖外部变量,不声明直接使用,就会使用外部作用域的变量;
返回值:
函数没有return时返回None,如果有多个,返回元组
与方法的区别:
class()对象.def引用时叫方法,class.def时叫函数。
高阶函数:
定义:
接受函数为参数,或者把函数作为结果返回的函数,如map,filter和reduce
替代品:
列表推导(python2)和生成器表达式(python3)
其他的如reduce,用于求和时用内置的sum性能更高
特殊属性:
__annotations__ dict 参数和返回值的注解
__call__ method-wrapper 实现() 运算符;即可调用对象协议
__closure__ tuple 函数闭包,即自由变量的绑定(通常是None)
__code__ code 编译成字节码的函数元数据和函数定义体
__defaults__ tuple 形式参数的默认值
__get__ method-wrapper 实现只读描述符协议(参见第20 章)
__globals__ dict 函数所在模块中的全局变量
__kwdefaults__ dict 仅限关键字形式参数的默认值
__name__ str 函数名称
__qualname__ str 函数的限定名称,如Random.choice,类.方法
函数参数:
语法:
tag(name, *content, cls=None, **attrs):
参数分类:
1.定位参数,如name
可通过没有关键字参数传入f(1,),也可通过关键字参数f(cls=1,name=2),定位参数通过没有关键字参数传入后,不可再通过关键字参数传入
2.可变位置参数,
明确指定名称的参数后面的任意个参数会被*content 捕获,存入一个元组。content
只能出现在函数定义中最后一个位置参数后面
3.仅限关键字参数(强制关键字参数)
只能作为关键字参数传入,一定不会捕获未命名的定位参数
需放到前面有*的参数后面,不然只是普通的定位参数,或者放一个*,f(a, *, b)
# 不一定要有默认值,用于强制使用函数的某些参数关键字参数传递,会比使用位置参数表意更加清晰,比使用**kwargs参数更容易理解help
4.可变关键字参数
没有明确指定名称的关键字参数会被**attrs 捕获,存入一个字典。不能与位置参数重名。
指定名称的关键字参数会绑定到对应的具名参数上
只能出现在最后一个参数上面
默认参数:
要放到参数列表最后,参数的值仅仅在函数定义的时候赋值一次
默认值如果是可变类型,得注意修改
测试某个可选参数是否被使用者传递进来:
某个默认值比如None、0 或者False 值来测试用户提供的值(因为这些值都是合法的值,是可能被用户传递进来的)都不行,用私有对象实例即可
_no_value = object(),object 是python 中所有类的基类,没有实例字典,不能设置值,只有内存id,适合测试同一性
参数类型:
引用类型:
概述:
行参将会复制一份内存地址。
相当于别名。
例外情况(元组):
无法修改那些元组元素的标识(即不能把一个对象替换成另一个对象),比如数字、元素,否则报错。
可以对列表等可变元素进行append、+=等操作。
推荐:
传入可变类型参数,且内部有修改时,灵活使用深浅复制。
值类型:
复制一份。
匿名函数lambda:
lambda 表达式中的外部变量是一个自由变量,类似闭包
在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。
如何在定义时绑定:
类似def的闭包,将变量作为参数传递,稍不同的是形式
a = lambda y, x=x: x + y # 第一个x是位置参数,第二个x才是外部变量
例子:
funcs = [lambda x: x+n for n in range(5)]
# 会创建5个表示式,n指向的是外部变量,会在执行时才去绑定
函数内省:
含义:
获取函数所需要的参数名称,然后从http请求获取对应的参数,传给函数,从而不用触碰请求对象
方法:
__defaults__:保存着定位参数和关键字参数的默认值,有默认值的必须排在定位参数后面,否则报错
__kwdefaults__:
__code__:值是一个code 对象引用
__code__.co_varnames:除了形参,还有函数定义体中创建的局部变量。参数名称是前N 个字符串
__code__.co_argcount:参数N的多少,这里不包含前缀为* 或** 的变长参数
# 要从后向前扫描才能把__code__.co_varnames参数名称和__defaults__默认值对应起来
inspect模块
from inspect import signature
sig = signature(func) #
sig.return_annotation 返回值注解
sig.parameters.items() 一个有序映射,把参数名和inspect.Parameter 对象对应起来。各个Parameter 属性也有自己的属性
param.name
param.kind
POSITIONAL_OR_KEYWORD 可以通过定位参数和关键字参数传入的形参(多数Python 函数的参数属于此类)。
VAR_POSITIONAL 定位参数元组,即可变位置参数
VAR_KEYWORD 关键字参数字典
KEYWORD_ONLY 仅限关键字参数(Python 3 新增)。0
POSITIONAL_ONLY 仅限定位参数;目前,Python 声明函数的句法不支持,但是有些使用C 语言实现且不接受关键字参数的函数(如divmod)支持。
param.default # 特殊的inspect._empty 值表示没有默认值
param.annotation # Python3新的注解句法提供的函数签名元数据注解,没有则是inspect._empty
# 有序映射 odict_items([('a', <Parameter "a">), ('wargs', <Parameter "*wargs">), ('b', <Parameter "b=1">)])
bound_types = sig.bind_partial(int,z=int)
部分绑定,将参数名以函数签名中相同顺序映射到指定的类型值上面去
bound_types.arguments:将参数名以函数签名中相同顺序映射到指定的类型值上面去
bound_args = sig.bind(**kwargs)
把任意个参数绑定到签名中的形参上,所用的规则与实参到形参的匹配方式一样。没有绑定失败则error,可以用来调用函数前验证参数
bound_args.arguments:一个OrderedDict 对象,显示参数的名称和值
inspect.getmembers()
用于获取对象的所有属性,第二个参数是可选的判断条件(一个布尔值函数)
inspect.getmembers(promotions, inspect.isfunction)
应用:
*args 和**kwargs 的强制参数签名
1.先定好要进行检测的参数
from inspect import Signature, Parameter
parms = [ Parameter('x', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42),
Parameter('z', Parameter.KEYWORD_ONLY, default=None) ]
sig = Signature(parms)
2.使用sig绑定检测传入参数
sig.bind(*args, **kwargs)
for name, value in bound_values.arguments.items():
print(name,value)
示例:
def make_sig(*names):
parms = [Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names]
return Signature(parms)
class Structure:
__signature__ = make_sig() # 继承来实现,或者通过元类的__new__方法
def __init__(self, *args, **kwargs):
bound_values = self.__signature__.bind(*args, **kwargs)
for name, value in bound_values.arguments.items():
setattr(self, name, value)
# Example use
class Stock(Structure):
__signature__ = make_sig('name', 'shares', 'price') # 要指定要检测的参数
# 在类定义阶段使用函数创建好签名,这样在类实例化阶段,init就会进行类型检测
与类初始化工厂函数相比:
代码更简洁,用到Signature和Parameter模块,比setattr更好
也可用元类来替代继承:
clsdict['__signature__'] = make_sig(*clsdict.get('_fields',[]))
__signature__:
当我们自定义签名的时候,将签名存储在特定的属性__signature__ 中通常是很有用的。
这样的话,在使用inspect 模块执行内省的代码就能发现签名并将它作为调用约定。
inspect.signature(Stock)
函数注解:
为函数声明中的参数和返回值附加元数据。
方式:
:之后增加注解表达式。如果参数有默认值,注解放在参数名和= 号之间。
如果想注解返回值,在) 和函数声明末尾的: 之间添加-> 和一个表达式。
# 最常用的类型是类(如str 或int)和字符串(如'int > 0')。可以使用任意类型的对象给函数添加注解。
def clip(text:str, max_len:'int > 0'=80) -> str:
获取:
fun.__annotations__
signature(func).items()[0][1].annotation
支持函数式编程的包:
operator模块:
1.为多个算术运算符提供了对应的函数,从而避免编写lambda a, b: a*b 这种平凡的匿名函数。
2.替代从序列中取出元素或读取对象属性的lambda 表达式
itemgetter(item,*items) 返回索引item位置的元素,多个items时返回元组
attrgetter(attr,*attrs) 根据名称提取对象的属性,多个参数时返回元组
3. methodcaller 自行创建函数
语法:
func = operator.methodcaller('distance', 0, 0)
返回一个实例,参数已保存,通过func(instance)执行call方法,等同于 obj.distance(0,0)
方式:getattr(obj, self._name)(*self._args, **self._kwargs)
示例:
from operator import methodcaller
upcase = methodcaller('upper') # 直接使用函数str.upper()也可
upcase("czl")
# hiphenate = methodcaller('replace', ' ', '-') # 指定部分参数,与functools.partial函数类似
functools模块:
1. functools.partial高阶函数
用于部分应用一个函数,固定原函数的某些参数。
扩展:对于
第一个参数是一个可调用对象,后面跟着任意个要绑定的定位参数和关键字参数。
picture = partial(tag, 'img', cls='pic-frame')
picture.func # 原函数
picture.args # 固定参数,不含关键字参数,如只有('img',)
应用:
- 减少普通函数调用时传递参数,如sort的key=func
- 减少类__init__初始化时所需参数,当类实例化时,拿到参数
- 微调其他库函数所使用的回调函数的参数 p.apply_async(add, (3, 4), callback=partial(output_result, log=log))
扩展:让回调函数拥有额外的状态值(而不是简单的固定值),以便在它的内部使用到
方法一:
使用一个类的绑定方法来代替一个简单函数。通过类来保存额外状态值。
p.apply_async(add, (3, 4), callback=obj.func) # 在方法内部就能访问类保存的状态
方法二:
一个单方法的类可以用闭包来替换
handler = make_handler() # 返回闭包
apply_async(add, (2, 3), callback=handler) # 闭包能自动捕获所有被使用到的变量,无需去担心如何去存储额外的状态信息(代码中自动判定)。
方法三:
协程,将闭包的内联回调函数改为一个函数,通过yield来执行状态的修改获取等
def make_handler():
sequence = 0
while True:
result = yield # apply_async(add, (2, 3), callback=handler.send)send触发回到原函数
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
- 传递参数给类,当类实例化时,拿到参数 serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED:'))
# 第二个参数会被调用,(),并传参ack
与lambda比较:
很多时候partial() 能实现的效果,lambda 表达式也能实现。
如:
points.sort(key=lambda p: distance(pt, p))借助闭包实现变量传递,但会显得比较臃肿
2. functools.partialmethod 函数
部分应用一个方法,类似
3. functools.total_ordering
使用一等函数实现设计模式:
“策略”模式
含义:
定义一系列算法,根据传入信息的不同而选择不同的策略算法
重构:
原本的算法类,仅一个方法,每次得实例化(静态方法则不需),消耗大。
策略模式搭配享元模式,共享具体策略对象,可同时共享使用,从而不用每次实例化
改用函数来实现策略,而函数只需在编译模块时创建一次,无需使用享元模式,本身即可共享且能同时使用
最优策略选择
[promo1,promo1] 用list存储函数
return max()
找到全部策略
1. globals()函数
返回字典,对应的value即函数的引用地址,if endswith筛选即可
2. 单独的模块定义所有的策略函数,把best_promo排除
inspect.getmembers函数
inspect.isfunction
# 这个模块中的所有属性都得是策略函数
3.使用装饰器添加函数名
无需使用特殊的名称以方便查找
突出了被装饰的函数的作用,还便于临时禁用某个促销策略
可以在其他模块中定义
命令模式
目的是解耦调用操作的对象(调用者)和提供实现的对象(接收者)
含义:
不同的接收者,传Command类的实例到不同的command子类,子类execute方法执行实例.execute方法,执行对应的命令
重构:
不为调用者提供一个Command 实例,而是给它一个函数,只需command()执行即可
闭包:
概述:
一种函数
指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量,称为自由变量。
条件:
1.必须有一个内嵌函数(函数里定义的函数) # 才有可能需要处理不在全局作用域中的外部变量
2.内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量
3.外部函数必须返回内嵌函数——必须返回那个内部函数
用途:
1.当闭包执行完后,仍然能够保持住当前的运行环境。
2.闭包可以根据外部作用域的局部变量来得到不同的结果
闭包变量的修改与访问:
可变类型可以直接更新,不可变类型需要借助nonlocal声明
对于python2,没有nonlocal声明,用字典或者实例属性即可
应用:
1.将单方法的类转换成函数
一个单方法类的原因是需要存储某些额外的状态来给方法使用。
比如,定义UrlTemplate 类的唯一目的就是先在某个地方存储模板值,以便将来可以在open() 方法中使用。
使用一个内部函数或者闭包的方案通常会更优雅一些。
示例:
class foo:
def __init__(self,template):
self.template=template # 需要存储某些额外的状态来给方法使用
def print_name(self,**name):
print(self.template.format_map(name))
def func(template): # 通过闭包来存储状态
def print_name(**name)
print template.format_map(name)
return print_name # 返回闭包
func("name is {name}")(name="czl")
装饰器:
本质:
嵌套函数的外函数有func变量,外函数的返回值替换原函数,内函数可以使用func变量
特性:
1.能把被装饰的函数替换成其他函数。
2.装饰器在加载模块时立即执行。
保留函数元信息:
重要的元信息比如名字__name__、文档字符串__doc__、注解__annotations__和参数签名(函数的各个参数)
通过@functools.wraps(func),__wrapped__ 直接访问被包装函数
带参数流程:
1.先执行@一句,使用了传入的参数,返回一个函数v,形成闭包
2.@v # 等价于 == > func = wrapper(func)
def wrapper(func):
print "[DEBUG]: enter {}()".format(func.__name__)
registry.append(func) # 这种用途在于把函数添加到某种中央注册处,例如把URL 模式映射到生成HTTP 响应的函数上的注册处。
return func # 原封不动地返回被装饰的函数
def wrapper(func):
def inner(*args, **kwargs): # 被装饰的函数需要传入参数时,这里接收
print "[DEBUG]: enter {}()".format(func.__name__)
return func(*args, **kwargs)
return inner
装饰器本身带参数时,多一层嵌套,用来接收,装饰时返回的函数为装饰函数。称为装饰器工厂函数,调用它返回真正的装饰器。
def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print "[{level}]: enter function {func}()".format(level=level,func=func.__name__)
return func(*args, **kwargs)
return inner_wrapper
return wrapper # 当带参数的装饰器被打在某个函数上时,它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。
本质:
相当于f = logging(1)(func)
本质:
外部函数传入被装饰函数名,内部函数返回装饰函数名。
类装饰器:
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
而callable对象除了函数外,还有如类的__call__(),推荐构建工业级装饰器的方法
class logging(object):
def __init__(self, func):
wraps(func)(self) # 然后通过self.__wrapped__就能访问到原函数,这样就不用保存在self.func
self.func = func
def __call__(self, *args, **kwargs):
print "[DEBUG]: enter function {func}()".format(func=self.func.__name__)
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
if instance is None:
return self # 通过类进行访问的时候,访问的是本身,这样可以访问到描述符类的属性
else:
return types.MethodType(self, instance) # 将托管类实例绑定给描述符实例,这样描述符()就会自动多传一个托管类实例参数self
实现了__call__就可以用在普通函数上,如果要用在类实例方法上,得实现__get__方法
原因:
class Spam:
@Profiled
def bar(self, x):
print(self, x)
@Profiled会返回一个实例,赋值给span实例的类属性,如果实现了__get__就是一个描述符协议,如果没实现就是普通的属性
如果不实现__get__方法,那么spam().bar获取类属性的时候,获取的只是另一个实例,另一个实例的__call__方法不会有self参数
与函数装饰器的比较:
def profiled(func):
ncalls = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal ncalls
ncalls += 1
return func(*args, **kwargs)
wrapper.ncalls = lambda: ncalls # 通过原函数.ncalls()就可以访问到自由变量ncalls的值
return wrapper
# 装饰器的本身是运行装饰器函数,运行返回的结果就是一个闭包,inner函数的指针 + func这个引用变量
# 比如函数装饰器,运行后返回inner替代原来的函数,下一次运行时就是inner()了。
# 相同的,类装饰器就是先实例化,下一次函数运行时,就是实例的call方法执行。
类中装饰器:
除了@foo这种类装饰器,原理是__call__方法执行外,还可以类的其他部分,因为类.方法或实例.方法=函数
如:
class A:
def decorator1(self, func): # 实例调用
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
@classmethod
def decorator2(cls, func): # 类调用
@wraps(func)
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
应用:
@property的property其实就是一个类,类的方法getter(), setter(),deleter() , 每一个方法都是一个装饰器
first_name = property()
@first_name.getter
def first_name(self):
pass
@first_name.setter
原因:
一个类的各种不同的装饰器方法能够在关联的property实例上(类中保存)操作它的状态。
需要在多个装饰器中记录或绑定信息时,可以用这个方法
注意事项:
设计到继承A时,继承的子类B不能直接B.warp使用A的warp装饰器,因为B在定义好方法后才会通过元类创建B这个类,才能使用B的方法。
在这之前,类B还没被创建
带参数的类装饰器:
在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。
class logging(object):
def __init__(self, level='INFO'):
self.level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print "[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__)
func(*args, **kwargs)
return wrapper #返回函数
# 带参数的装饰器因为多了一个括号,解释时本身就运行一次了,所以多了一层嵌套即可。
标准库中内置的装饰器:
functools.wraps # __name__ 和__doc__ 属性
1.保留重要的元信息比如名字__name__、文档字符串__doc__、注解__annotations__和参数签名,
2.还能通过__wrapped__ 直接访问被包装函数,直接访问未包装的原始函数在调试、内省和其他函数操作时是很有用的
如果有多个包装器,那么访问__wrapped__ 属性的行为是不可预知的(3.3会略过所有包装层,而3.4只略过最外层)
内置的装饰器@staticmethod 和@classmethod把原始函数存储在属性__func__
3.还能让被装饰函数正确暴露底层的参数签名信息,signature(countdown)
functools.lru_cache
作用:
把耗时的函数的结果保存起来缓存,避免传入相同的参数时重复计算
使用:
functools.lru_cache(maxsize=128, typed=False)
maxsize 参数指定存储多少个调用的结果,应该设为2的幂
typed 参数如果设为True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如1 和1.0)区分开。
原理:
使用字典存储结果,而且键根据调用时传入的定位参数和关键字参数创建,
所以被lru_cache 装饰的函数,它的所有参数都必须是可散列的。
functools.singledispatch
单分派泛函数,装饰后普通函数会变成泛函数(generic function),根据第一个参数的类型,以不同方式执行相同操作的一组函数。
使用:
@singledispatch装饰func后,再使用@func.register(«type»)装饰专门函数
可以叠放多个register装饰器,支持不同类型
类型type应该写抽象基类,如numbers.Integral 和abc.MutableSequence,而不是具体实现,如int 和list,代码支持的兼容类型更广泛
优点:
模块化扩展
functools.total_ordering
每个比较操作都需要实现一个特殊方法来支持。
只需定义一个__eq__() 方法,外加其他方法(__lt__, __le__, __gt__, or__ge__) 中的一个即可。然后装饰器会自动为你填充其它比较方法。
原理:
就是定义了一个从每个比较支持方法到所有需要定义的其他方法的一个映射而已。
比如你定义了__le__() 方法,那么它就被用来构建所有其他的需要定义的那些特殊方法。
自定义属性的装饰器:
给内层warp函数(@返回的那个函数应用)将通过nonlocal修改最外层带参数装饰器的变量的方法设置为函数的属性
def set_level(newlevel):
nonlocal level
level = newlevel
这样,直接通过原函数func.set_level直接就能修改闭包的自由变量(隔了两层还是闭包,自由变量)
在嵌套装饰器的情况下,原函数.访问函数即func.set_level仍然适用,但warp.set_level就不适用了,会隐藏底层属性
带可选参数的装饰器:
def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
# 如果带括号,func还没传进来,得返回一个函数,既能下次接收func,又能保留这次除了被包装函数外其他参数
return partial(logged, level=level, name=name, message=message)
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
可以通过@logged或者@logged(level=logging.CRITICAL, name='example')的方式使用
利用装饰器做类型检查:
利用带参数的装饰器,三层函数嵌套
def typeassert(*ty_args, **ty_kwargs):
def decorate(func):
if not __debug__: # 使用-O 或-OO 参数的优化模式执行程序时,去掉装饰器的功能
return func
sig = signature(func)
bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments # 可以只指定部分参数类型
@wraps(func)
def wrapper(*args, **kwargs):
bound_values = sig.bind(*args, **kwargs) # 不允许忽略任何参数
for name, value in bound_values.arguments.items():
if name in bound_types: # 如果已经指定了类型检测
if not isinstance(value, bound_types[name]): # 类型不对
raise TypeError('Argument {} must be {}'.format(name, bound_types[name]))
return func(*args, **kwargs)
return wrapper
return decorate
小瑕疵:
对于有默认值的参数并不适用,参数有默认值,那么可以不传这个参数,而是使用默认值,不会类型检测
装饰器为被包装函数添加参数:
def optional_debug(func):
if 'debug' in inspect.getargspec(func).args: # getfullargspec也可以,检测函数的参数签名
raise TypeError('debug argument already defined') # 检测是否已经定义了这个参数
@wraps(func)
def wrapper(*args, debug=False, **kwargs): # 添加了强制关键字参数
if debug:
print('Calling', func.__name__)
return func(*args, **kwargs)
sig = inspect.signature(func)
parms = list(sig.parameters.values())
parms.append(inspect.Parameter('debug',inspect.Parameter.KEYWORD_ONLY,default=False)) # 添加新增的参数签名
wrapper.__signature__ = sig.replace(parameters=parms) # 赋值给wrapper,以后通过原函数访问=wrapper
return wrapper
类装饰器扩展类功能:
def log_getattribute(cls): # 通常可以作为其他高级技术比如混入或元类的一种非常简洁的替代方案。
orig_getattribute = cls.__getattribute__
def new_getattribute(self, name):
print('getting:', name)
return orig_getattribute(self, name)
cls.__getattribute__ = new_getattribute # 交换值的方式
return cls
常用函数:
高阶函数:
map()
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
map(function, iterable, ...)
zip()
用法一
a=[1,2,3] b=[11,22,33]
list(zip(a,b))=[(1,11),(2,22),(3,33)]
用法二
a = [(1,2,3),(11,22,33)]
list(zip(*a))=[(1,11),(2,22),(3,33)]
注:
zip 有个奇怪的特性:当一个可迭代对象耗尽后,它不发出警告就停止
from itertools import zip_longest
zip_longest(range(3), 'ABC', [0.0, 1.1, 2.2, 3.3], fillvalue=-1)
使用可选的fillvalue(默认值为None)填充缺失的值,因此可以继续产出,直到最长的可迭代对象耗尽。
reduce函数
reduce(function, iterable[, initializer])
函数 f 必须接收两个参数,reduce()对list的每个元素反复调用函数f,并返回最终结果值。
如果有第三个参数,则函数f的第一个参数是这个,第二个参数是list的第一个,第二次循环的第二个参数是list的第二个
加密函数:
md5加密:
import hashlib
app_id = 'asdasdadsd'
m = hashlib.md5(b'asd') # 这里加盐
m.update(bytes(app_id,encoding='utf-8'))
authkey = m.hexdigest()
print(authkey)
sha1加密
比MD5更加安全,但SHA1的运算速度就比MD5要慢
hashlib.sha1("".join(sorted([token,timestamp,nonce])).encode()).hexdigest()
偏函数:
参数可以在函数被调用之前提前获知,通过这个参数重新绑定一个新的函数,然后去调用这个新函数。
import functools
def index(a1,a2):
return a1+a2
new_func = functools.partial(index,22)
ret = new_func(1)
print(ret)
内置函数:
处理属性:
dir() 返回所有的属性和方法,不指定实例时列出当前作用域中的名称,多于.__dict__,而且部分对象没有__dict__方法
getattr(object, name[, default]) 获取的属性可能来自对象所属的类或超类
hasattr(object, name) 实现方法是调用getattr(object, name) 函数,看看是否抛出AttributeError 异常
setattr(object, name, value)
vars([object]) 返回属性和值,没有参数时等于locals(),有参数时等于object.__dict__,如果__slot__后vars(obj)会报错
要求必须有__dict__属性
all() 可传列表,所有元素不为空时返回True
any() 任一元素不为空时返回True
chr() ascii表
eval() 将字符串str当成有效的表达式来求值并返回计算结果。
enumerate() 将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
locals() 局部变量
globals() 返回当前模块的全局变量字典
zip(('a','b'),(1,2)) 生成对应的元组,传可循环对象即可如str
ord('a') 字符串对应的ascii数字
pow() 乘方,三个参数时x**y%z
reversed() 反转
vars()
next()
all(iterable)和any(iterable):
如果iterable 的每个元素都是真值,返回True;all([]) 返回True。
all(a == b for a, b in zip(self, other))
只要iterable 中有元素是真值,就返回True;any([]) 返回False。
print函数:
1.输出到文件
print('Hello World!', file=f)
2.其他分隔符
print('ACME', 50, 91.5, sep=',')
3.其他行尾符
print('ACME', 50, 91.5, sep=',', end='!!\n'),默认是\n
str()和repr():
1. 内建函数str()和repr() 或反引号操作符(``)可以方便地以字符串的方式获取对象的内容、类型、数值属性等信息。
2、str()函数得到的字符串可读性好(故被print调用)
3、repr()函数得到的字符串通常可以用来重新获得该对象,通常情况下 obj==eval(repr(obj)) 这个等式是成立的。
这两个函数接受一个对象作为其参数,返回适当的字符串。
4、事实上repr()和``做一样的事情,返回一个对象的“官方”字符串表示。其结果绝大多数情况下(不是所有)可以通过求值运算(内建函数eval())重新得到该对象。
str()则不同,它生成一个对象的可读性好的字符串表示,结果通常无法用eval()求值,但适合print输出。
深浅拷贝:
浅拷贝:copy() 只拷贝第一层
深拷贝:deepcopy() 克隆一份
eval函数:
eval(expression, globals=None, locals=None)
将字符串str当成有效的表达式来求值并返回计算结果。
globals和locals参数是可选的,如果提供了globals参数,那么它必须是dictionary类型;
如果提供了locals参数,那么它可以是任意的map对象。
示例:
self.expression_code_dict[expression] = compile(expression, filename="decision_tree", mode="eval")
child_node = self.child_dict.setdefault(expression, TreeNode())
for expression, child_node in self.child_dict.items():
if eval(self.expression_code_dict[expression], {}, feature_dict):
return child_node
# 编译字符串表达式,建立索引,以方便eval比较
1. model_3.0.0 <= 1 不能这样比较,可以用_替换或者以3为后缀,或者 '' 引起来
2. 如果eval的字段在locals都没,会报nameError错误
compile函数:
compile(source, filename, mode[, flags[, dont_inherit]])
source -- 字符串或者AST(Abstract Syntax Trees)对象。。
filename -- 代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
mode -- 指定编译代码的种类。可以指定为 exec, eval, single。
flags -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。。
flags和dont_inherit是用来控制编译源码时的标志
exec函数:
动态执行python代码
可以执行复杂的python代码,而不像eval函数那样只能计算一个表达式的值
exec(source, globals=None, locals=None, /)
source:必选参数,表示需要被指定的python代码。它必须是字符串或code对象。
如果source是一个字符串,该字符串会先被解析为一组python语句,然后执行。
如果source是一个code对象,那么它只是被简单的执行。
globals:全局变量字典
locals:局部变量字典,运行将会使用这个字典,而不是默认的locals()获取到的当时局部变量字典拷贝,变量的修改将会同步到字典,
exec函数的返回值永远为None。
在全局命名空间运行与函数内运行的区别:
全局命名空间运行后,新增的变量会被保留
在局部函数内运行后,全局肯定访问不了,函数内在运行后也访问不了:
原因:
在函数里面,默认传递给exec()的局部范围是拷贝实际局部变量组成的一个字典(代码层面等用的都是这个)。
如果exec()如果执行了修改操作,这种修改后的结果对实际局部变量值是没有影响的。影响的只是拷贝字典。
局部的变量还是指向实际局部变量内存空间中的值
解决:
通过loc=locals()获取到这一瞬间局部变量的拷贝,而这个拷贝将会默认传给exec()。
执行exec()后,通过loc(拷贝字典引用)拿到变量修改后的字典。
locals()每次被调用的时候,会获取实际局部变量值中的值并覆盖拷贝字典中loc相应的变量。(不存在则不会覆盖)
enumerate函数:
enumerate(sequence, [start=0])
用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
一般用在for循环中得到计数,利用它可以同时获得索引和值
迭代器和生成器:
可迭代对象:
解释器需要迭代对象x 时,会自动调用iter(x)
内置的iter 函数有以下作用。
(1) 检查对象是否实现了__iter__ 方法,如果实现了就调用它,获取一个迭代器(如果没有实现next方法,则获取不了,抛出异常)。
(2) 如果没有实现__iter__ 方法,但是实现了__getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引0 开始)获取元素。
(3) 如果尝试失败,Python 抛出TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中C 是目标对象所属的类。
任何Python 序列都可迭代的原因是,它们都实现了__getitem__ 方法。
# 在鸭子类型理论中,不仅要实现__iter__ 方法,还要实现__getitem__ 方法,而且__getitem__ 方法的参数是从0 开始的整数(int),这样才认为对象是可迭代的。
# 而在白鹅类型理论中,如果实现了__iter__ 方法,那么就认为对象是可迭代的。但其实如果没实现__getitem__,返回不了值
# 实现了__iter__,可通过issubclass(Sentence, abc.Iterable)判断是否可迭代
检查对象是否可迭代,最准确的做法是调用iter(x)函数,看是否抛出TypeError 异常
迭代器:
标准的迭代器接口有两个方法:
__iter__方法:返回self自身实例或者其他迭代器,在Iterable 类中定义
__next__方法:返回self下一个可用的元素,如果没有元素了,抛出StopIteration 异常。在collections.abc.Iterator 抽象基类中制定,具体类须实现
# __iter__方法返回的self或者其他迭代器需要实现该方法,这是协议规定
# 如果返回self,需要自定义的类写好__next__方法,迭代返回并处理好StopIteration异常
# 而yield和yield from搭配,不用繁琐的实现next和StopIteration异常,不用在迭代处理过程维护大量的状态信息
判断:
检查对象x是否为迭代器最好的方式是调用isinstance(x, abc.Iterator),得益于Iterator.__subclasshook__ 方法
特性:
迭代器调用next不断消耗(有个属性index计数当前位置),无法还原,若想再次重新,不能传迭代器
此外,next(迭代器,None)可以在迭代结尾返回None,用来自定义break
比较:
迭代器可以迭代,但是可迭代的对象不是迭代器。
如只实现了__getitem__方法的可迭代对象,序列
注意:
可迭代对象通过__iter__方法获取到的迭代器一定不能是自身的迭代器,即不能实现__next__方法。否则不支持多种遍历,会共享同一个__next__的index
# 一般返回另一个实现了迭代器接口协议的类实例,这样不共享
做法:
1. iter获取的是一个迭代器类的实例
2. iter__获取的是yield生成器对象,这时__iter方法是生成器函数
iter用法:
1.用来触发迭代器,iter(迭代器)
2.用来替代while循环直到返回需要的值,iter(callable,value)以创建迭代器,即下面的哨符
生成器:
种类:
有yield关键字的函数
实现了__iter__包含yield关键字的方法的类的实例,再iter(实例)返回才是生成器(调用__iter__方法)# 与函数比较,能访问属性,其他方法
生成器表达式 (x for x in 生成器函数,返回生成器)
生成器工厂函数
生成器是迭代器,实现了迭代器接口,
会不断执行函数定义体中的下一个yield语句并暂停返回产出的值,最终执行完定义体后return时这个生成器会抛出StopIteration 异常。
编写风格:
1.传统的拉取式(迭代器)yield
2.推送式,xx = yield yy # 协程的简单形式
3.任务式,有多个推送式,等待上一个推送式任务执行完后,执行下一个推送式任务。搭配yield from实现并发 # 并发协程
标准库中的生成器函数 # 创建的对象属于语言内部的types.GeneratorType类型
1.用于过滤的生成器函数
从输入的可迭代对象中产出元素的子集,而且不修改元素本身
itertools compress(it, selector_it)
并行处理两个可迭代的对象;如果selector_it 中的元素是真值,产出it 中对应的元素
itertools dropwhile(predicate, it)
处理it,跳过predicate 的计算结果为真值的元素,然后产出剩下的各个元素(不再进一步检查)
(内置) filter(predicate, it)
把it 中的各个元素传给predicate,如果predicate(item)返回真值,那么产出对应的元素;如果predicate 是None,那么只产出真值元素
itertools filterfalse(predicate, it)
与filter 函数的作用类似,不过predicate 的逻辑是相反的:predicate 返回假值时产出对应的元素
itertools islice(it, stop) 或islice(it,start, stop, step=1)
迭代器和生成器切片,会不断消耗index
产出it的切片,作用类似于s[:stop] 或s[start:stop:step],不过it 可以是任何可迭代的对象,而且这个函数实现的是惰性操作
itertools takewhile(predicate, it)
predicate 返回真值时产出对应的元素,然后立即停止,不再继续检查
2.用于映射的生成器函数
itertools accumulate(it, [func])
产出累积的总和;如果提供了func,那么把前两个元素传给它,然后把计算结果和下一个元素传给它,以此类推,最后产出结果
(内置) enumerate(iterable, start=0)
产出由两个元素组成的元组,结构是(index, item),其中index 从start 开始计数,item 则从iterable 中获取
(内置) map(func, it1, [it2, ..., itN])
把it 中的各个元素传给func,产出结果;如果传入N 个可迭代的对象,那么func 必须能接受N 个参数,而且要并行处理各个可迭代的对象
itertools starmap(func, it)
把it中的各个元素迭代传给func,产出结果;输入的可迭代对象应该产出可迭代的元素iit,然后以func(*iit) 这种形式调用func
3.用于合并的生成器函数
itertools chain(it1, ..., itN)
先产出it1 中的所有元素,然后产出it2 中的所有元素,以此类推,无缝连接在一起
itertools chain.from_iterable(it)
产出it 生成的各个可迭代对象中的元素,一个接一个,无缝连接在一起;it 应该产出可迭代的元素,例如可迭代的对象列表
itertools product(it1, ...,itN, repeat=1)
计算笛卡儿积:从输入的各个可迭代对象中获取元素,合并成由N个元素组成的元组,与嵌套的for 循环效果一样;repeat 指明重复处理多少次输入的可迭代对象
# 包含所有组合
(内置) zip(it1, ..., itN)
并行从输入的各个可迭代对象中获取元素,产出由N 个元素组成的元组,只要有一个可迭代的对象到头了,就默默地停止
itertools zip_longest(it1, ...,itN, fillvalue=None)
并行从输入的各个可迭代对象中获取元素,产出由N 个元素组成的元组,等到最长的可迭代对象到头后才停止,空缺的值使用fillvalue 填充
4.把输入的各个元素扩展成多个输出元素的生成器函数
itertools combinations(it,out_len) 组合
把it 产出的out_len 个元素组合在一起,然后产出 # 不含相同元素,如不含(a,a)以及不含不同次序但相同元素
itertools combinations_with_replacement(it, out_len)
把it 产出的out_len 个元素组合在一起,然后产出, # 包含相同元素的组合,如含(a,a),不含不同次序但相同元素
itertools count(start=0, step=1)
从start 开始不断产出数字,按step指定的步幅增加
itertools cycle(it)
从it 中产出各个元素,存储各个元素的副本,然后按顺序重复不断地产出各个元素
itertools permutations(it,out_len=None) 排列
将it产出的元素随机选出out_len个,排列在一起,然后产出这些排列;out_len的默认值等于len(list(it))
# 包含不同次序但相同元素的组合,如(a,b)和(b,a),不含相同元素的组合,如不含(a,a)
# 数量大小:product > permutations\combinations_with_replacement > combinations
itertools repeat(item, [times])
重复不断地产出指定的元素,除非提供times,指定次数,常见用途:为map函数提供固定参数
# combinations、combinations_with_replacement 和 permutations 生成器函数,连同product 函数,称为组合学生成器
5.用于重新排列元素的生成器函数
itertools groupby(it,key=None)
产出由两个元素组成的元素,形式为(key, group),其中key是分组标准,group 是生成器,用于产出分组里的元素
# it得先排序好才行
(内置) reversed(seq)
从后向前, 倒序产出seq 中的元素;seq 必须是序列, 或者是实现了_reversed__ 特殊方法的对象
itertools tee(it, n=2)
产出一个由n 个生成器组成的元组,每个生成器用于单独产出输入的可迭代对象中的元素
yield from i
替代了for j in i:yield j
会创建通道,把内层生成器直接与外层生成器的客户端联系起来。
把生成器当成协程使用时,这个通道特别重要,不仅能为客户端代码生成值,还能使用客户端代码提供的值。
归约函数
(内置) all(it)
it 中的所有元素都为真值时返回True,否则返回False;all([]) 返回True
(内置) any(it)
只要it 中有元素为真值就返回True,否则返回False;any([]) 返回False
(内置) max(it, [key=,][default=])
返回it 中值最大的元素;*key 是排序函数,与sorted 函数中的一样;如果可迭代的对象为空,返回default
(内置) min(it, [key=,][default=])
返回it 中值最小的元素;*key 是排序函数,与sorted 函数中的一样;如果可迭代的对象为空,返回default
functools reduce(func, it,[initial])
把前两个元素传给func,然后把计算结果和第三个元素传给func,以此类推,返回最后的结果;
如果提供了initial,把它当作第一个元素传入
# 其他内置函数都可以使用reduce实现,但all和any会短路,确定了结果就立刻停止迭代器
(内置) sum(it, start=0)
it 中所有元素的总和,如果提供可选的start,会把它加上(计算浮点数的加法时,可以使用math.fsum 函数提高精度)
其他函数
sorted
会构建并返回真正的列表
reversed
生成器函数,只有序列的长度已知时才能工作,按需产出各个元素,因此无需创建反转的副本
iter(func, sentinel)
一个鲜为人知的特性:
传入两个参数,第一个是常规的函数或任何可调用的对象,用来创建迭代器。第二个是哨符,当可调用的对象返回这个值时才触发迭代器抛出StopIteration 异常
def d6():
return randint(1, 6)
d6_iter = iter(d6, 1) # 返回一个callable_iterator 对象,与常规的迭代器一样,一旦耗尽就没用了
for roll in d6_iter:
print(roll)
用途:
1.处理大型数据集,用生成器函数实现读逻辑,然后渐进写入,管道方式处理数据的一个示例,有效节省内存
示例:
def gen_find(filepat, top): # 负责不断返回当前路径下的所有文件名
for path, dirlist, filelist in os.walk(top):
for name in fnmatch.filter(filelist, filepat):
yield os.path.join(path,name)
def gen_opener(filenames):
for filename in filenames: # filenames为生成器
if filename.endswith('.gz'):
f = gzip.open(filename, 'rt')
elif filename.endswith('.bz2'):
f = bz2.open(filename, 'rt')
else:f = open(filename, 'rt')
yield f # 拿到上个生成器yield返回的一个文件名
f.close()
def gen_concatenate(iterators):
for it in iterators: # iterators为生成器
yield from it # 相当于for i in it:yield it,这里拿到的文件句柄f本身是个生成器
def gen_grep(pattern, lines):
pat = re.compile(pattern)
for line in lines: # 连接上面的yield from
if pat.search(line):
yield line # 拿到链式管道末端的一个文件名,经过管道一系列处理后,得到一个生成器,用来迭代
2.协程用途:生成器.send()能够传参给yield返回
3.展开嵌套的序列
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
# 将字符串和字节排除在可迭代对象外,防止将它们再展开成单个的字符
for循环首先调用iter()方法,即x.__iter__(),得到一个迭代器对象,再调用对象__next__()方法取值,能捕捉StopIteration异常
可迭代对象包括:迭代器、列表、字典等
迭代器包括生成器,经典的迭代器模式(实现了协议的两个接口)则不是生成器。
语义对比:
1.接口
Python 的迭代器协议定义了两个方法:__next__ 和__iter__。
生成器对象实现了这两个方法,因此从这方面来看,所有生成器都是迭代器。
2.实现方式
生成器这种Python 语言结构可以使用两种方式编写:含有yield 关键字的函数,或者生成器表达式。
特别的,而调用生成器函数或者执行生成器表达式得到的生成器对象属于语言内部的GeneratorType 类型,GeneratorType 类型的实例实现了迭代器接口
# 所以,生成器不一定是GeneratorType 类型的对象,yield就不是
经典的迭代器模式,或者使用C 语言编写扩展,不是生成器
3.概念
迭代器用于遍历集合,从中产出元素。迭代器可能相当复杂,比如遍历树状数据结构;都是从现有的数据源中读取值;
而且,调用next(it) 时,迭代器不能修改从数据源中读取的值,只能原封不动地产出值。
而生成器可能无需遍历集合就能生成值,例如range 函数。即便依附了集合,生成器不仅能产出集合中的元素,还可能会产出派生自元素的其他值。
如enumerate函数
文件操作:
文件模式:
r 只读,默认模式,文件必须存在,不存在则抛出异常
w 只写,不可读;不存在则创建;覆盖
x 只写,不可读;不存在则创建,存在则报错,python2没有
a 追加只写模式,不可读;不存在则创建;存在则只追加内容
+ 表示可以同时读写某个文件
t 文本模式,默认
b 二进制模式,图片视频等
打开:
open('cafe.txt', 'w', encoding='utf_8') # 打开时明确传入encoding=参数
操作方法:
f.read() #读取所有内容,光标移动到文件末尾,可传参:t模式表示字符,b模式表示字节
f.readline() #读取一行内容,光标移动到第二行首部
f.readlines() #读取每一行内容,存放于列表中
f.write('1111\n222\n') #针对文本模式的写,需要自己写换行符
f.write('1111\n222\n'.encode('utf-8')) #针对b模式的写,需要自己写换行符
f.writelines(['333\n','444\n']) #文件模式
f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) # b模式
f.readable() #文件是否可读
f.writable() #文件是否可读
f.closed #文件是否关闭
f.encoding #如果文件打开模式为b,则没有该属性
f.flush() #立刻将要写入的文件内容从内存刷到硬盘
f.name
f.tell() #目前光标所在位置
f.seek(1,1) #将光标移到字节为N的位置,第二个参数为模式,0表示绝对位置,1相对,2倒序,一般seek(-1,2)
f.truncate(3) #从开始位置开始剪切N个字节
编码问题:
读写操作默认使用系统编码,可以通过调用sys.getdefaultencoding() 来得到。
如果遇到读写编码error,可以指定errors='replace'或'ignore'
open('sample.txt', 'rt', encoding='ascii', errors='replace')
换行符识别问题:
在Unix 和Windows 中是不一样的(分别是\n 和\r\n )
读取时,Python 可以识别所有的普通换行符并将其转换为单个\n 字符。
写入时,会将换行符\n 转换为系统默认的换行符。
如果不希望这种默认的处理方式(读取替代),可以给open() 函数传入参数newline=''
with open('somefile.txt', 'rt', newline='') as f:
文件打开形式:
推荐for i in f: 会一行行读入内存
每次迭代大小固定的文件
with open('somefile.data', 'rb') as f: # 二进制文件常用这个方式,文本常用一行行(默认)
records = iter(partial(f.read, RECORD_SIZE), b'') # 利用了iter哨符的作用
# 如果总记录大小不是块大小的整数倍的话,最后一个返回元素的字节数会比期望值少。
print输出到文件:
print('Hello World!', file=f) # 其中的f必须是文本模式,b就不行
二进制IO:
读取的二进制数据,即字节,进行索引和迭代操作,返回的是字节的值,而不是字节字符串
读取和写入时,要进行编解码
数组和C 结构体类型能直接被写入,而不需要中间转换为自己对象。
适用于任何实现了被称之为”缓冲接口”的对象,这种对象会直接暴露其底层的内存缓冲区给能处理它的操作。
二进制数据的写入就是这类操作之一。
如f.write(buf)
很多对象还允许通过使用文件对象的readinto() 方法直接读取二进制数据到其底层的内存中去,无需额外内存
如f.readinto(array.array('i', [0, 0, 0, 0, 0, 0, 0, 0]))
搭配buf = bytearray(record_size)存储二进制字节,memoryview零复制对缓冲区执行切片、修改等。
# 需要格外小心,因为这种方法通常具有平台相关性,并且可能会依赖字长和字节顺序(高位优先和低位优先)
# 使用f.readinto() 时需要注意的是,你必须检查它的返回值,也就是实际读取的字节数。
# 如果字节数小于缓冲区大小,表明数据被截断或者被破坏了
内存映射二进制文件:
使用:
size = os.path.getsize(filename)
fd = os.open(filename, os.O_RDWR)
m = mmap.mmap(fd, size, access=access) # 可以作为一个上下文管理器来使用,底层的文件会被自动关闭
模式:
读写:access
只读:mmap.ACCESS_READ
只修改本地:mmap.ACCESS_COPY # 不修改文件
优点:
1.任何的修改内容都会复制回原来的文件中,支持内存视图memoryview解析,使用切片操作进行访问
2.文件并没有被复制到内存缓存或数组中,仅为文件内容保留了一段虚拟内存。
当你访问文件的不同区域时,这些区域的内容才根据需要被读取并映射到内存区域中。
3. 多个Python 解释器内存映射同一个文件,得到的mmap对象能够被用来在解释器直接交换数据(多个进程间通信),修改会直接呈现在其他解析器
文件名编码问题:
忽略系统的默认编码来解码,使用一个原始字节字符串来指定一个文件名即可
with open(b'jalapen\xcc\x83o.txt') as f
# 读取目录并通过原始未解码方式处理文件名可以有效的避免不符合默认编码而出现的解析异常
打印不合法的文件名:
对于不合规范的文件名(没有正确编码),默认从文件名中获取未解码的字节值如\xhh 并将它映射成Unicode 字符\udchh 表示的所谓的”代理编码”
这时能正确open或者操作文件名,但输出时代理字符对因为缺了前半部分会异常
解决:
repr(name)[1:-1]
或用对应的编码格式进行解码
temp=filename.encode(sys.getfilesystemencoding(), errors='surrogateescape')
temp.decode('latin-1')
# surrogateescape是Python 在绝大部分面向OS的API 中所使用的错误处理器,对于解码出错时默认的
# 将出错字节存储到一个很少被使用到的Unicode 编码范围内,在编码时将那些隐藏值又还原回原先解码失败的字节序列
# 不仅对于OS API 非常有用,也能很容易的处理其他情况下的编码错误。
修改已打开文件的编码:
使用detach()方法移除掉已存在的文本编码层,并使用新的编码方式代替
io.TextIOWrapper(sys.stdout.detach(), encoding='latin-1') # io.TextIOWrapper() 对象包装能够添加编码
IO系统层次:
f 编码和解码Unicode 的文本处理层
f.buffer 处理二进制数据的带缓冲的I/O 层,直接TextIOWrapper这层会破坏f的原始值并关闭底层文件,需detach()断开最顶层
# 文本文件是通过在一个拥有缓冲的二进制模式文件上增加一个Unicode 编码/解码层来创建。
# buffer 属性指向对应的底层文件。如果你直接访问它的话就会绕过文本编码/解码层。
# 类似的,能够通过读取文本文件的buffer 属性来读取二进制数据。
f.buffer.raw 表示操作系统底层文件描述符的原始文件
这种方法还能改变文件行处理、错误机制以及文件处理的其他方面
io.TextIOWrapper(sys.stdout.detach(), encoding='ascii',errors='xmlcharrefreplace')
字节写入文本文件:
利用f.buffer层
此外,sys.stdout.buffer.write(b'Hello\n')也能绕过输出的文本编码层。# 默认情况下文本模式
将文件描述符包装成文件对象:
定义:
文件描述符是一个由操作系统指定的整数,用来指代某个系统的I/O 通道,如文件、管道、套接字
使用:
可以通过使用open() 函数来将其包装为一个Python 的文件对象
如:
fd = os.open('somefile.txt', os.O_WRONLY | os.O_CREAT)
f = open(fd, 'wt',closefd=False) # 当高层的文件对象被关闭或者破坏的时候,底层的文件描述符不会被关闭
应用:
1.将一个类文件接口作用于一个以不同方式打开的I/O 通道上,如管道、套接字等
示例:
client_sock, addr = sock.accept() # socket接收到请求信息,生成一个对应的文件描述符
client_in = open(client_sock.fileno(), 'rt', encoding='latin-1',closefd=False) # 以r模式打开
client_out = open(client_sock.fileno(), 'wt', encoding='latin-1',closefd=False) # 以w模式打开
for line in client_in:
client_out.write(line) # 类文件接口的作用
client_out.flush()
# 只适用于基于Unix 的系统,以跨平台,请使用套接字对象的makefile() 方法
2.允许以不同于第一次打开文件的方式使用它
bstdout = open(sys.stdout.fileno(), 'wb', closefd=False) # 默认文本模式
局限:
并不是所有的文件模式都被支持,并且某些类型的文件描述符可能会有副作用(特别是涉及到错误处理、文件结尾条件等等的时候)。
在不同的操作系统上这种行为也是不一样
临时文件:
tempfile模块
tempfile.TemporaryFile 创建一个匿名的临时文件,如TemporaryFile('w+t'),支持跟内置的open() 函数一样的参数
NamedTemporaryFile('w+t',delete=False)
有名字的临时文件,f.name属性包含了该临时文件的文件名
delete指定文件关闭时是否会被自动删除掉
tempfile.TemporaryDirectory()
创建一个临时目录
mkstemp() 和mkdtemp()
底层的创建临时文件和目录函数,需要自己将它转换为一个真正的文件对象和清理
tempfile.gettempdir()
系统默认的位置用来创建临时文件
# 所有和临时文件相关的函数都允许你通过使用关键字参数prefix 、suffix 和dir来自定义目录以及命名规则
类文件IO操作:
io.StringIO(str)
创建类文件对象操作字符串数据,模拟普通文件
s = io.StringIO() # 创建
s.write('Hello World\n') # 写入
print('This is a test', file=s) # 能用于file
s.getvalue() # 获取全部值
s.read(4) # 读取4个
s.read() # 读取全部
io.BytesIO(str)
操作二进制数据
当你想模拟一个普通的文件的时候StringIO 和BytesIO 类是很有用的。比如,在单元测试中,你可以使用StringIO 来创建一个包含测试数据的类文件对象,
这个对象可以被传给某个参数为普通文件对象的函数。
没有正确的整数类型的文件描述符,不能在那些需要使用真实的系统级文件如文件,管道或者是套接字的程序中使用。
读取压缩文件:
gzip模块
with gzip.open('somefile.gz', 'rt') as f:
bz2模块
with bz2.open('somefile.bz2', 'rt') as f: # wt是写入,二进制格式是rb wb
模式:
默认是二进制模式,不能接受文本
压缩等级:
compresslevel,1-9,越高性能越差,数据压缩程度越高
with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:
特性:
可以作用在一个已存在并以二进制模式打开的文件上
f = open('somefile.gz', 'rb')
with gzip.open(f, 'rt') as g:
text = g.read()
这样就允许gzip 和bz2 模块可以工作在许多类文件对象上,比如套接字,管道和内存中文件等。
zipfile模块
打开:
azip = zipfile.ZipFile('bb.zip','r')
文件夹和文件列表:
azip.namelist()
所有文件信息:
azip.filelist # 返回一个ZipInfo对象列表,包含filename,compress_type,filemode,file_size,compress_size
# <ZipInfo filename='history_label/r_feature_id_application_1.sql' compress_type=deflate filemode='-rw-rw-r--' file_size=6779 compress_size=1367>
或:
azip.infolist
获取单个文件信息:
azip.getinfo('bb/aa.txt')
import
Python 2.3版中已经可以从zip文件中导入模块了。
一个zip文件相当于一个目录,因此只要将zip文件加入到sys.path变量中即可以从其中导入模块。
pattern = re.compile(r"(feature)/(\w+)/((?!__init__.py|handler.py)\w+)\.py")
for py_file in zipfile.ZipFile(os.path.dirname(path)).namelist():
result = pattern.match(py_file)
if result:
importlib.import_module(".".join(result.groups()))
读取文件内容:
a = azip.read('bb/cc.txt').decode('utf-8')
# config_parser.read_string(azip.read("system_config/system.conf").decode())
或更底层的
b = azip.open(azip_info)
print(b.read().decode('utf-8'))
shutil模块
压缩:
shutil.make_archive('archive_name', 'zip', r'F:\IDE Setting')
解压:
shutil.unpack_archive(r'D:\bb.zip')
常用模块:
time模块:
time.time() 返回当前时间的时间戳#1473525444.037215,在某些场景下需要int()防止科学计数法e的形式
time.localtime() 返回结构化时间#time.struct_time(tm_year=2016, tm_mon=9,..),默认传当前时间戳
time.gmtime([secs]) 将一个时间戳转换为UTC时区(0时区)的struct_time
time.mktime(t) 将一个struct_time转化为时间戳。默认按照本地系统的时区来转换。如果传了不同时区的,得出的时间戳有差异。
time.asctime() 把一个表示时间的元组或者struct_time表示为这种形式:'Sun Jun 20 23:21:05 1993'。
time.ctime([secs]) 把一个时间戳表示为这种形式:'Sun Jun 20 23:21:05 1993'。
time.strftime("%Y-%m-%d %X", time.localtime()) 将一个代表时间的元组或struct_time转化为字符串的格式时间。
time.strptime('2011-05-05 16:37:06', '%Y-%m-%d %X') 将一个格式时间转化为struct_time。
time.clock()
类似time.time(),计算的时间精度因操作系统的不同会有所不同
time.perf_counter()
精度和性能都比较高的计时器
调用一次 perf_counter(),从计算机系统里随机选一个时间点A,计算其距离当前时间点B1有多少秒。
当第二次调用该函数时,默认从第一次调用的时间点A算起,距离当前时间点B2有多少秒。两个函数取差,即实现从时间点B1到B2的计时功能。
time.process_time()
不包含休眠时间,只计算当前进程所花费的CPU 时间,两次调用
datetime模块:
datetime模块定义了5个类,分别是
1.datetime.date:表示日期的类
2.datetime.datetime:表示日期时间的类
datetime.datetime.now().date():返回当前日期时间的日期部分
datetime.datetime.now().time():返回当前日期时间的时间部分
datetime.datetime.now():返回当前系统时间
datetime.datetime.strftime():由日期格式转化为字符串格式
datetime.datetime.strptime(): 由字符串格式转化为日期格式,通过timetuple()可以转换为struct_time
# 注:strptime是用C写的,多线程会import没完成就返回,从而AttributeError,加线程锁即可
# 或者在线程启动前,使用datetime.datetime.strptime("2019-02-25","%Y-%m-%d")
# strptime是用纯python实现的,性能较差,通过split字符串然后datetime(int(year_s), int(mon_s), int(day_s))更快
datetime.fromtimestamp(timestamp[, tz]):根据时间戮创建一个datetime对象,参数tz指定时区信息;
datetime.utcfromtimestamp(timestamp):根据时间戮创建一个UTC datetime对象;
默认tz=None,转换为当前操作系统的设定时区
datetime.timetuple()
time.mktime(dtime.timetuple())转时间戳
3.datetime.time:表示时间的类
4.datetime.timedelta:表示时间间隔,即两个时间点的间隔
两个日期相减,得到datetime.timedelta对象,通过.seconds(秒部分的差值)\.days\.total_seconds(总时间间隔转秒数)
示例:min_date = (datetime.datetime.now()-datetime.timedelta(days=90)).strftime("%Y-%m-%d")
min_timestamp = int(time.mktime(time.strptime('2018-11-15',"%Y-%m-%d")))
5.datetime.tzinfo:时区的相关信息
# 更复杂的可以用dateutil模块,dateutil.relativedelta.relativedelta()可以支持months,而timedelta不支持
# 对于寻找上个星期几,可以用第三方包python-dateutil
常用转换:
1.datetime类型转时间戳:
one["jobTime"] = time.mktime(one["jobTime"].timetuple())*1000
datetime.datetime.strptime(a,"%Y-%m-%dT%H:%M:%S.%f%z").timestamp() # datetime类型直接转,python3.3才有
2.字符串转时间戳:
one["duration"] = time.mktime(one["jobStatusDetail"][0]["startedAt"].timetuple())*1000
关于时间戳与datetime:
1. 把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),
当前时间就是相对于epoch time的秒数,称为timestamp。
2. timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的。
所以计算机存储的时间以timestamp表示的,全球各地的计算机在任意时刻的timestamp都是完全相同的(假定时间已校准)。
3. timestamp是一个浮点数,它没有时区的概念,而datetime是有时区的。
dateutil模块:
执行更加复杂的日期操作,比如处理时区,模糊时间范围,节假日计算等等
python-dateutil包:
第三方包
calendar模块:
获取日历相关信息
calendar.monthrange(start_date.year, start_date.month) 返回包含星期和该月天数的元组。
pytz 模块:
所有涉及到时区的问题,提供了Olson 时区数据库,它是时区信息的事实上的标准
方法:
central = timezone('US/Central') # 确定时区,pytz.utc为UTC时间
loc_d = central.localize(date) # 时区对象.localize
loc_d.astimezone(timezone('Asia/Kolkata')) # 时区对象转其他时区
central.normalize(loc_d + timedelta(minutes=30)) # 能修正一些时区跳跃问题,比如美国夏令时于3 月13 日凌晨2:00向前跳跃1个小时
# 更好的方法是不在本地时区上操作,而是utc时间上操作再转换本地时间
pytz.country_timezones['IN'] # 用ISO 3166 国家代码作为关键字去查阅字典pytz.country_timezones
random模块:
random.random() # 产生 0 到 1 之间的随机浮点数
random.randint(1,3) # 产生 1 到 3 的一个整数型随机数
random.randrange(1,3) # 生成一个从1到3的间隔为1的随机整数
random.choice([1,'23',[4,5]]) # 从序列中随机选取一个元素
random.sample([1,'23',[4,5]],2) # 可以从指定的序列中,随机的截取指定长度的片断,不作原地修改。
random.uniform(1,3) # 产生1到3之间的随机浮点数,区间可以不是整数,均匀分布随机数
random.gauss(1,3) # 正态分布随机数
random.shuffle(li) # 将序列a中的元素顺序打乱
random.seed() # 初始化种子,如果种子确定了,之后生成的随机数确定
os模块:
os.path.basename # 文件名
os.path.dirname # 文件名前面的路径
os.path.join # 兼容unix和win
os.path.expanduser # 扩展全路径,如~会扩展为当前用户路径
os.path.splitext # 分割文件后缀
os.path.split() # 将文件名和路径分割开。
os.path.exists # 测试一个文件或目录是否存在
os.path.isfile
os.path.isdir
os.path.islink
os.path.realpath # 获取link对应的文件,真实路径
os.path.getsize
os.path.getmtime # 修改时间
os.path.abspath(__file__) # pycharm默认返回的__file__就是绝对路径,其他软件不是,所以一般写上abspath
os.path.normpath() # 用来返回正常路径,可以解决双斜杆、对目录的多重引用的问题等
os.path.commonprefix # 获取共同前缀
os.listdir # 获取当前path下的文件和目录列表,采用默认系统编码sys.getfilesystemencoding()来解码
os.scandir # 返回当前路径的content列表,通过is_file()或者is_dir()来判断类型
文件名的匹配:
glob或fnmatch模块
pyfiles = glob.glob('somedir/*.py') # 列举该路径所有对应后缀的文件,绝对路径
fnmatch(name, '*.py')] # 是否满足后缀
os.stat # 获取文件/目录信息,返回一个对象,有st_size,st_mtime等属性
os.fork() # 复制
os.getcwd() # 获得当前工作目录
os.chdir('/') # 改变当前脚本工作目录;相当于shell下cd,python shell环境下,import、os.listdir()会实时更新(import是因为shell加入'.'这个path)
# 而脚本环境下,import还是原来的(加入的是绝对路径,所以没随着更新),只有os.listdir()更新
os.umask(0) # 重置文件权限掩码,修改文件模式,让进程有较大权限,保证进程有读写执行权限,这个不是一个好的方法。
os.setsid() # 创建了一个全新的进程会话,并设置子进程为首领。它会设置这个子进程为新的进程组的首领,并确保不会再有控制终端。
# 需要将守护进程同终端分离开并确保信号机制对它不起作用。
os.getpid()
os.remove(pidfile) # 删除一个文件
os.removedirs(path) #删除空文件夹
os.rmdir(path) #删除空文件夹
os.dup2(f.fileno(), sys.stdin.fileno()) # sys.stdout 使用的原始文件会被关闭并由新的来替换,任何用于文件编码或文本处理的标准I/O 流还会保留原状。
os.kill(int(f.read()), signal.SIGTERM)
atexit.register() # 函数注册了一个函数在Python 解释器终止时执行。抛出的SystemExit()会执行这个函数注册的清理操作
sz = os.get_terminal_size() # 当前终端的大小
sz.columns
sz.lines
sys模块:
sys.path.append()
sys.stdout.write()
sys.stdout.flush() # 输出不缓存,直接输出
sys._getframe(0) # 获取该函数栈
sys._getframe(1) # 获取调用该函数的函数的栈,拿到栈后,
.f_code.co_filename # 当前文件名
.f_code.co_name # 当前函数名
.f_lineno # 当前行号
.f_locals # 当前局部变量
json模块:
用于字符串和python最基本的数据类型间进行转换,字符串能在不同语言之间交互
支持类型:
JSON 编码支持的基本数据类型为None ,bool ,int ,float 和str ,以及包含这些类型数据的lists,tuples 和dictionaries。
dumps方法支持str、int、bool、dict、list,对于str,dumps('s')会转为'"s"'
loads方法支持'str、int、bool、dict、list',注意最外层要有单引号,里面的值相当于eval()执行结果,'s'这种是错的
方法:
json.dumps # 注意dict中不能包含byte类型,
json.loads # 要想loads数据,要求json格式的数据的key必须为双引号,单引号报错,要求str字符串,python3.6以后会将bytes自动转换为str类型
json.dump(data,f)
json.load(f)
pickle模块:
用于python特有的类型(序列化后对象,字节类型\x80\x03开头的,不能decode)和python的数据类型间进行转换
方法:
pickle.dumps # 将一个对象转储为一个字节
pickle.loads
pickle.dump(data,f) # 将一个对象保存到一个文件中
pickle.load(f)
可序列化对象:
1.普通的python对象,如序列,实例
2.还能序列化函数,类,还有接口(from xx import yy),但是结果数据仅仅将它们的名称编码成对应的代码对象。
当数据反序列化回来的时候,会先假定所有的源数据时可用的。模块、类和函数会自动按需导入进来。
# pickle 在加载时有一个副作用就是它会自动加载相应模块并构造实例对象。能被利用来执行随意指定的系统命令。
不可序列:
1.通常是那些依赖外部系统状态的对象,比如打开的文件,网络连接,线程,进程,栈帧,线程锁等等
(可以做成类,那么实例就是依赖状态,虽然实例可以序列化,但每次反序列都重新状态,无中间态)
解决:
用户自定义类可以通过提供__getstate__() 和__setstate__() 方法来绕过这些限制。(保存外部状态和类的名称,如果类不存在了,会失败)
实例:
class foo:
def __init__(self, n):
self.thr = threading.Thread(target=self.run)
def __getstate__(self): #
return self.n # 序列化时保存n
def __setstate__(self, n):
self.__init__(n)
def run(self):
while self.n > 0:
print('T-minus', self.n)
self.n -= 1
time.sleep(5)
2.在python2中额外不能序列化实例的方法,类的方法。(__getstate__解决不了实例方法不能序列化问题)
解决:(写个函数嵌套一下即可)
def run(instance,*args,**kwargs):
return instance.small_batch_qry_feature(*args,**kwargs) # 多进程,传实例作为序列化参数
注:如果实例属性含线程锁,将不能pickle
3.lambda函数
4.模块
pickle.dumps(math)会失败,而pickle.dumps(math.cos)则可以,loads则自动假设math模块可以import导入
5.带有compile编译字节的属性
会报cannot pickle code object错误
局限:
1.对于大型的数据结构比如使用array 或numpy 模块创建的二进制数组效率并不是一个高效的编码方式。
如果需要移动大量的数组数据,最好是先在一个文件中将其保存为数组数据块或使用更高级的标准编码方式如HDF5 (需要第三方库的支持)。
2.如果需要长期存储数据的时候不应该选用它,如果源码变动了,存储数据可能会被破坏并且变的不可读取。
坦白来讲,对于在数据库和存档文件中存储数据时,你最好使用更加标准的数据编码格式如XML,CSV 或JSON。
shelve模块:
shelve模块比pickle模块简单,只有一个open函数,返回类似字典的对象,可读可写;
key必须为字符串,而值可以是python所支持的数据类型
如:f['stu1_info']={'name':'alex','age':'18'}
xml模块:
通过<>节点来区别数据结构的
logging模块:
1.简单使用 # DEBUG < INFO < WARNING < ERROR < CRITICAL
logging.debug('debug message') # logging.log(logging.DEBUG, "This is a debug log.") 用来自定义level日志
logging.info('info message')
logging.warning('warning message')
logging.error('error message') # 仅展示错误type和错误信息,不显示错误的具体栈信息
logging.critical('critical message')
logger.exception('this is an exception message') # 自动捕捉异常,等于logging.error('error message',exc_info=True)
关于exc_info, stack_info, extra关键词参数的说明:
exc_info: 其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
stack_info: 其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
extra: 这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。
2.配置
logging.basicConfig(level=logging.DEBUG, # 一个一次性的简单配置工具,创建了实例,使用logging即可, 再getLogger()会再创建新实例
format='%(asctime)s %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s - %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S', # 如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
filename='/tmp/test.log',
filemode='w') # 通过basicConfig创建的实例是root logger,以后的所有输出都算它一份
# basicConfig()默认创建streamHandler,指定filename时创建fileHandler,然后添加到root中
其他的format参数
%(levelno)s:打印日志级别的数值(10, 20, 30, 40, 50)
%(levelname)s:打印日志级别的名称,('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
%(name)s:所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger,且该实例是以单例模式存在的。
%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s:打印当前执行程序名,pathname的文件名部分,包含文件后缀
%(module)s:filename的名称部分,不包含后缀
%(funcName)s:打印日志的当前函数
%(lineno)d:打印日志的当前行号,调用日志记录函数的源代码所在的行号
%(asctime)s:打印日志的时间,如:2003-07-08 16:49:45,896
%(created)f:日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
%(thread)d:打印线程ID
%(threadName)s:打印线程名称
%(process)d:打印进程ID
%(message)s:打印日志信息
此外,还通过getLogger()后详细配置,或者通过配置文件来进行配置
3.logger对象
logger = logging.getLogger('name') # 在同一个程序中一直都使用同名的logger,其实会拿到同一个实例,默认为root
# 默认的logging使用的是root这个logger。
# logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children
# logging.getLogger("root")获取到的不是root的这个logger
# 此外,多进程使用有问题
fh = logging.FileHandler('test.log') # 创建一个handler,用于写入日志文件
ch = logging.StreamHandler() # 再创建一个handler,用于输出到控制台
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') #指定日志显示格式。
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象,logger.handlers是一个列表,可通过removeHandler(fh)或者logger.handlers=[fh]来移除
logger.addHandler(ch)
logger.setLevel(logging.INFO) # logger对象的level决定使用logger.info等时会不会触发下一步
# handler对象的level默认为0,触发下一步后对每个handler与msg的level进行比较,msg的level较高时才会记录
filter = logging.Filter('mylogger')
logger.addFilter(filter)
第二个logger对象以第一个root为父节点,会打印两次;name相同表示父节点相同
使用时,只需logger = logging.getLogger("App.UI"),如果存在则会获取到同一个实例,这样能够使用相同的配置
logger.info()
4.格式化
service_name = "Booking"
logger.error('%s service is down!' % service_name) # 使用python自带的字符串格式化,不推荐
logger.error('%s service is down!', service_name) # 使用logger的格式化,推荐
logger.error('%s service is %s!', service_name, 'down') # 多参数格式化
logger.error('{} service is {}'.format(service_name, 'down')) # 使用format函数,推荐
5.logging日志模块四大组件
日志器 Logger 提供了应用程序可一直使用的接口
child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。
因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,
只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。
我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
处理器 Handler 将logger创建的日志记录发送到合适的目的输出
logging.StreamHandler 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。
logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
logging.WatchedFileHandler
用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流
linux/unix系统由于newsyslog或者logrotate的使用会导致文件改变,而window不会
logging.handlers.RotatingFileHandler
将日志消息发送到磁盘文件,并支持日志文件按大小切割
当文件大小达到或者超过maxBytes时,就会新创建一个日志文件
logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割
按照when和interval定时生成新日志文件,保留设置的数量日志文件。
logging.handlers.MemoryHandler
在内存中缓存logging的日志,周期flush到指定类的handler函数来处理。
形式: class ErrorHandle:
def handle(self, record):
print record
mh = logging.handlers.MemoryHandler(100, target=ErrorHandle()) # 100指的是should flush的list的长度 # 几乎每条都发
logging.handlers.HTTPHandler 将日志消息以GET或POST的方式发送给一个HTTP服务器
logging.handlers.SMTPHandler 将日志消息发送给一个指定的email地址
logging.NullHandler
一个空处理器默认会忽略调用所有的日志消息。
该Handler实例通常被想使用logging的library开发者使用来避免'No handlers could be found for logger XXX'信息的出现。
过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
class logging.Filter(name='')
filter(record)
一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:
'A.B','A.B,C','A.B.C.D','A.B.D',而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。
如果name的值为空字符串,则允许所有的日志事件通过过滤。
filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。
如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。
格式器 Formatter 决定日志记录的最终输出格式
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
关系:
1.日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
2.不同的处理器(handler)可以将日志输出到不同的位置;
3.日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
4.每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
5.每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
6.logger的层次和handlers定位
输出所有的logger,不包含rootLogger
for name in logging.Logger.manager.loggerDict.keys():
logger = logging.getLogger(name)
print('name = %s, logger = %s' % (name, logger),logger.handlers)
查看一个logger的层级关系
logger.parent
logger.root # 如果root输出了,也会显示为子logger的名字,如INFO django.server xxx
logger.getChild("cvat")
定位多个重复输出:
打印父层级的logger,将其的handler设置为[]看看
configparser模块 :
config = configparser.ConfigParser()
config["xx"] = xx # 设置信息
config.write(f) # 写入文件
connfig.read(f) # 读取文件
格式:
[mysql]
host=xxx
port=xx
user=xx
passwd=xx
获取时通过字典获取,connfig.get("mysql","host")即可拿到
hash模块:
将数据库加密,验证登录的时候再将密码加密比较,这样都是密文
obj = hashlib.md5("serity".encode()) # 加盐,能避免撞库,因为固定的字符串对应的密文一定,加盐后不同
obj.update('admin'.encode())
obj.hexdigest()
subprocess模块:
subprocess模块允许一个进程创建一个新的子进程,通过管道连接到子进程的stdin/stdout/stderr,
获取子进程的返回值等操作。
a=subprocess.Popen('ls',shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
)
上下文管理器和with块:
上下文管理器对象存在的目的是管理with语句,就像迭代器的存在是为了管理for 语句一样。
with 语句的目的是简化try/finally 模式。
这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return 语句或sys.exit()调用而中止,也会执行指定的操作。
finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。
上下文管理器协议包含__enter__ 和__exit__ 两个方法。
with 语句开始运行时,会在上下文管理器对象上调用__enter__ 方法。
def __enter__(self)
with 语句运行结束后,会在上下文管理器对象上调用__exit__ 方法,以此扮演finally 子句的角色。
def __exit__(self, exc_type, exc_value, traceback):
有异常时,返回None,或者True 之外的值,with 块中的任何异常都会向上冒泡,传给with块报错。
exc_type
异常类(例如ZeroDivisionError)。
exc_value
异常实例。有时会有参数传给异常构造方法,例如错误消息,这些参数可以使用exc_value.args 获取。
traceback
traceback 对象
调用sys.exc_info()得到的就是这三个参数
过程:
1.执行with 后面的表达式,得到一个上下文管理器
2.上下文管理器调用__enter__,把return值绑定到目标变量上(as 子句)
# 可能会返回其他自定义的值,而不是上下文管理器self
3.在上下文管理器对象上调用__exit__ 方法
contextlib模块中的实用工具
closing
如果对象提供了close() 方法,但没有实现__enter__/__exit__ 协议,那么可以使用这个函数构建上下文管理器。
suppress
构建临时忽略指定异常的上下文管理器。
@contextmanager
这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了。
减少创建上下文管理器的样板代码量,只需实现有一个yield 语句的生成器,生成想让__enter__ 方法返回的值。
yield 语句前面的所有代码在with 块开始时(即解释器调用__enter__ 方法时)执行,
yield 语句后面的代码在with 块结束时(即调用__exit__ 方法时)执行。
contextlib.contextmanager 装饰器会把函数包装成实现__enter__ 和__exit__ 方法的类:
这个类的__enter__ 方法有如下作用。
(1) 调用生成器函数,保存生成器对象(这里把它称为gen)。
(2) 调用next(gen),执行到yield 关键字所在的位置。
(3) 返回next(gen) 产出的值,以便把产出的值绑定到with/as 语句中的目标变量上。
with 块终止时,__exit__ 方法会做以下几件事。
(1) 检查有没有把异常传给exc_type;如果有,调用gen.throw(exception),在生成器函数定义体中包含yield 关键字的那一行抛出异常。
(2) 否则,调用next(gen),继续执行生成器函数定义体中yield 语句之后的代码。
如果有异常了,yield那一行抛出,导致之后的语句无法执行,改用形式:
try:
yield
except ZeroDivisionError:
finally:
# 这里yield与迭代没有任何关系,而是协程
ContextDecorator
这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。
ExitStack
这个上下文管理器能进入多个上下文管理器。with 块结束时,ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的__exit__ 方法。
如果事先不知道with 块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。
trackback模块:
try捕捉异常时,能拿到最全的信息,与python命令行运行程序出现错误信息一致。
traceback.print_exc() :打印异常信息到标准错
traceback.format_exc() 返回字符串的标准错误信息
其他展示错误信息的方法:
sys模块
info = sys.exc_info() : 返回tuple, (type, value/message, traceback)
(<class 'TypeError'>, TypeError("'NoneType' object is not iterable",), <traceback object at 0x00000153D1500648>)
traceback.extract_tb(info[2])[0]
file, lineno, function, text
repr(e)
获取系统信息模块:
解释器相关信息
1.platform模块
platform.python_implementation() 返回当前解释器的种类CPython’, ‘IronPython’, ‘Jython’, ‘PyPy’.
platform.python_version() 返回解释器的版本 2.7.15
platform.python_build() 返回 (buildno, builddate)信息,('v2.7.15:ca079a3ea3', 'Apr 30 2018 16:30:26')
2.sys模块
sys.executable 返回解释器二进制文件的绝对路径 C:\Python27\python2.exe
sys.subversion 返回 (repo, branch, version) 第一个是解释器种类,如('CPython', '', '')
sys.version 字符串,"版本 (版本具体信息) [使用的编译器]"
2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)]
sys.version_info (major, minor, micro, releaselevel,serial)元组 sys.version_info(major=2, minor=7, micro=15, releaselevel='final', serial=0)
系统相关信息
1.platform模块
platform.system() 返回操作系统的名字,'Linux', 'Windows', or 'Java'
platform.version() 返回系统的版本 10.0.17763
platform.release() 返回系统的版本号 10
platform.machine() 返回机器的类型 AMD64
platform.node() 返回系统的网络名字 DESKTOP-KGULK3V
platform.platform(aliased=0, terse=0) 返回 system-release-version的字符串 'Windows-10-10.0.17763'
platform.processor() 返回系统的处理器名字 'Intel64 Family 6 Model 158 Stepping 9, GenuineIntel'
platform.uname() 返回 (system, node, release, version, machine, processor) 元组
2.sys模块
sys.platform 返回操作系统的名字,小写,如win32,java1.8.0_121
3.os模块
os.name 返回当前操作系统的类型posix , nt , java, 对应linux/windows/java虚拟机
os.uname() 返回(sysname, nodename, release, version, machine)
('Linux', 'risk1-172-31-9-229', '4.4.0-112-generic', '#135-Ubuntu SMP Fri Jan 19 11:48:36 UTC 2018', 'x86_64')
4.socket模块
socket.gethostname() 获取系统的hostname
socket.gethostbyaddr(socket.gethostname())
二分查找算法bisect模块 :
bisect 模块包含两个主要函数,bisect 和insort
bisect(haystack, needle,lo,li)
# 即bisect_right
在haystack(干草垛)里搜索needle(针)的位置,这个函数返回的位置前面的值,都小于或等于needle 的值。
返回的则是跟它相等的元素之后的位置
lo 的默认值是0,hi的默认值是序列的长度,即len() 作用于该序列的返回值。
bisect_left
返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素会被放置于它相等的元素的前面
示例:
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
insort(seq, item,lo,li)
# 即insort_right
把变量item 插入到序列seq 中,并能保持seq 的升序顺序。
相当于用bisect(haystack, needle) 查找位置index,再用haystack.insert(index,needle) 来插入新值。但速度更快
insort_left
timeit模块:
1. timeit.timeit(stmt='pass', setup='pass', timer=, number=1000000)
返回:返回执行stmt这段代码number遍所用的时间,单位为秒,float型
参数:stmt:要执行的那段代码
setup:执行代码的准备工作,初始化代码或构建环境导入语句,不计入时间,一般是import之类的
timer:这个在win32下是time.clock(),linux下是time.time(),默认的,不用管
number:要执行stmt多少遍
2. repeat(stmt='pass', setup='pass', timer=, repeat=3, number=1000000)
这个函数比timeit函数多了一个repeat参数而已,表示重复执行timeit这个过程多少遍,返回一个列表,表示执行每遍的时间
默认3次
3. timeit.default_timer()
默认的计时器
s1 = timeit.default_timer()
4. Timer类
Timer类里面的函数跟上面介绍的两个函数是一样一样的
Timer.timeit(number=1000000)
Timer.repeat(repeat=3,number=1000000)
5. 命令行调用
python -m timeit [-n N] [-r N] [-s S] [-t] [-c] [-h] [statement...]
-n N 执行指定语句的次数
-r N 重复测量的次数(默认3次)
-s S 指定初始化代码活构建环境的导入语句(默认pass)
python 3.3新增
-t 使用time.time() (不推荐)
-c 使用time.clock() (不推荐)
-v 打印原始计时结果
-h 帮助
dis模块:
反汇编函数
from dis import dis
dis('{1}')与dis('set([1])')
原理:
输出的原始字节码可以通过func.__code__.co_code获取
与串行端口的数据通信:
Python 内置的I/O 模块可以完成
对于串行通信最好的选择是使用pySerial 包
示例:
ser = serial.Serial('/dev/tty.usbmodem641',baudrate=9600,bytesize=8,parity='N',stopbits=1)
ser.write(b'G1 X50 Y50\r\n')
resp = ser.readline()
优点:
提供了对高级特性的支持(比如超时,控制流,缓冲区刷新,握手协议等等)
# 时刻记住所有涉及到串口的I/O 都是二进制模式的。
Signal信号包:
signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示:
singnal.signal(signalnum, handler) # signalnum为某个信号
handler对应三种情况:
- signal.SIG_IGN时,信号被无视(ignore)
- singal.SIG_DFL,进程采取默认操作(default)
- 为一个函数名时,进程采取函数中定义的操作。
def myhandler(signum,frame):
pass
# signum即信号编号(数字)
# frame为被信号中断那一时刻的栈帧。可以通过traceback.extract_stack(frame)解析,与sys._current_frames().items()相同
信号触发:(可用可不用,默认使用linux系统的信号)
signal.pause(5) # 来让该进程暂停以等待信号
signal.alarm(5) # 用于在一定时间之后,向进程自身发送SIGALRM信号
常用第三方库:
TQDM包:
能处理任何可迭代的对象,生成一个迭代器;使用这个迭代器时,显示进度条和完成全部迭代预计的剩余时间。
用法:
要获取一个能使用len 函数确定大小的可迭代对象,
或者在第二个参数中指定预期的元素数量。
cc_iter = tqdm.tqdm(cc_iter,total=len(cc_list)) #
for cc in cc_iter: # 每迭代完成一次,就更新进度条动画
func(cc)
原理:
这是显示文本式动画的诀窍所在:使用退格符(\x08)把光标移回来。
如:
write('\x08' * len(status))
再使用空格清除状态消息,把光标移回开头。
write(' ' * len(status) + '\x08' * len(status)) # 不清除的话如果长度短了,只会覆盖前面
空值转换:
NaN numpy pandas
None nan python
null json sql
NaN转nan:
pandas的to_dict()
nan转None:
pip install simplejson
simplejson.dumps(a,ignore_nan=True)
可以通过math.isnan()判断
剪裁图片:
from PIL import Image
img = Image.open("dog.png")
cropped = img.crop((1,15,300,314))# (left, upper, right, lower)
cropped.save("dog.png")
import matplotlib.pyplot as plt
from skimage import io,transform
img=io.imread("test.png")
# img = np.expand_dims(img, axis=2)
img=transform.resize(img,(24,72)) # transform.rescale(img,0.5)
plt.imshow(img,plt.cm.gray)
plt.show()
比cv2.resize和from PIL import Image im = Image.open("test.png") im=im.resize([72,24])效果要好
内存缓存工具:
from cachetools import TTLCache
a={}
a[1] = TTLCache(maxsize=1000, ttl=1) # maxsize为体积,ttl为时间,数据缓存在内存中,超时自动释放。set会更新,get不会更新缓存时间
a[1][1]=1
import time
time.sleep(2)
print(a[1][1]) # 消失了
获取文件的MIME Type
使用python内置模块mimetypes
print mimetypes.guess_type(filename)[0]
只能依赖文件后缀来识别类型
filetype
kind = filetype.guess('tests/fixtures/sample.jpg')
kind.mime
python-magic
magic.from_file("testdata/test.pdf")
依赖libmagic,需要安装
conda使用:
创建:
conda create -n myenv python=3.6 # 指定安装python
列表:
conda env list
激活:
conda activate test-env
指定环境执行:
在conda环境中执行一个可执行的程序
conda run -n test -- python --version
python GUI编程:
Tkinter模块
wxPython
Jython
yaml模块:
yaml.load()要比json.loads()耗时要多几倍
所以kubectl get命令解决优先考虑json模块来解析
数据处理:
csv数据:
列表形式:
import csv
with open('x.csv') as f:
f_csv = csv.reader(f)
headers = next(f_csv) # 获取第一行的列名,通过namedtuple('Row', headings)具名元组来下标访问,列名得是合法标识符
for row in f_csv: # 拿到的是一个列表,通过索引访问对应的列
# process_csv
对于列名不合法问题,需转换,如通过正则[ re.sub('[^a-zA-Z_]', '_', h) for h in next(f_csv) ]
字典形式:
f_csv = csv.DictReader(f)
能通过row['Symbol']访问
写入:
with open('stocks.csv','w') as f:
f_csv = csv.writer(f)
f_csv.writerow(headers)
f_csv.writerows(rows)
字典形式写入:
f_csv = csv.DictWriter(f, headers)
f_csv.writeheader()
f_csv.writerows(rows)
优点:
比自定义的split能自动处理一些棘手的细节问题,如","
csv 库可识别Microsoft Excel 所使用的CSV 编码规则,还能应用其他编码格式上,(如修改分割字符等)
f_tsv = csv.reader(f, delimiter='\t')
pandas包自带的函数pandas.read_csv()
读取JSON数据:
方法:
json.dumps()
可选indent,用于控制repr的行首距,美化输出
json.loads()
# 要想loads数据,要求json格式的数据的key必须为双引号,单引号报错
创建其他类型:
JSON解码会根据提供的数据创建dicts或lists。
如果你想要创建其他类型的对象,可以给json.loads()传递object_pairs_hook 或object_hook 参数
如:
json.loads(s, object_pairs_hook=OrderedDict)
data = json.loads(s, object_hook=JSONObject) # loads转换后的dict传给object_pairs_hook或object_hook作为参数
区别:
object_pairs_hook依据解码次序来生成key-value对list作为参数传递,
而object_hook则是整个字典传递,object_pairs_hook优先级高
json.dump(data,f) # 文件
json.load(f) # 文件
支持数据类型:
JSON 编码支持的基本数据类型为None,bool ,int ,float 和str,以及包含这些类型数据的lists,tuples 和dictionaries。
与python的差异:
True 会被映射为true,False 被映射为false,而None 会被映射为null
输出print:
使用pprint 模块的pprint() 函数来代替普通的print() 函数,会按照key 的字母顺序并以一种更加美观的方式输出
# 当数据的嵌套结构层次很深或者包含大量的字段可以用
不可JSON序列化类型:
对象实例(但可pickle)
解决:
可以提供一个函数,它的输入是一个实例,返回一个可序列化的字典。(保存类名和变量)
def serialize_instance(obj):
d = { '__classname__' : type(obj).__name__ } # 类名
d.update(vars(obj))
return d
def unserialize_object(d):
clsname = d.pop('__classname__', None) # 根据类名生成实例
if clsname:
cls = classes[clsname]
obj = cls.__new__(cls) # Make instance without calling __init__
for key, value in d.items():
setattr(obj, key, value) # 不是init,而是根据字典来setattr,因为init参数有次序。通过cls(d[key],)y也行
# 如果key顺序不知道,就只能setattr,不过一般知道
return obj
else:
return d
json.dumps(p, default=serialize_instance)
json.loads(s, object_hook=unserialize_object)
读取XML数据:
xml模块
使用:
from xml.etree.ElementTree import parse
doc = parse(text)
for item in doc.iterfind('channel/item'): # 所指定的标签名是起始元素的相对路径,搜索所有在channel 元素下面的item 元素
title = item.findtext('title') # 从已找到的item 元素位置开始搜索
parse解析整个XML 文档并将其转换成一个文档对象。然后,你就能使用find() 、iterfind() 和findtext() 等方法来搜索特定的XML 元素了。
这些函数的参数就是某个指定的标签名。
属性:
e = doc.find()
e.tag # 标签名字
e.text # 内部文本
e.get('some_attribute') # 获取属性值
lxml模块:
使用了和ElementTree 同样的编程接口,因此方法和属性都适用
from lxml.etree import parse
lxml 完全遵循XML 标准,并且速度也非常快,同时还支持验证,XSLT,和XPath 等特性。
增量解析
传统的doc = parse("x.xml")会先将整个XML 文件加载到内存中然后解析,耗内存大
而doc = iterparse(filename, ('start', 'end'))对XML 文档进行增量操作,返回形如(event, elem) 的元组
事件类型:
'start':每个元素第一次被创建并且还没有被插入其他数据(如子元素) 时被创建。
'end':某个元素已经完成时被创建。
'start-ns':命名空间开始
'end-ns':命名空间结束
示例:
def parse_and_remove(filename, path):
path_parts = path.split('/')
doc = iterparse(filename, ('start', 'end'))
next(doc) # Skip the root element
tag_stack = [] # 用于保存标签名,用来比较是否是给定的path
elem_stack = [] # 用于保存标签元素,doc.find()返回的对象
for event, elem in doc:
if event == 'start': # 新发现标签
tag_stack.append(elem.tag)
elem_stack.append(elem)
elif event == 'end': # 找到匹配的尾标签
if tag_stack == path_parts: # 符合路径
yield elem # 返回元素
elem_stack[-2].remove(elem) # 删除,以省内存
try:
tag_stack.pop() # 删除该尾标签对应的首标签
elem_stack.pop() # 删除元素
except IndexError:
pass # 文档底部
优缺点:
省内存,但性能慢了大概一倍
字典转xml
from xml.etree.ElementTree import Element
elem = Element(tag)
for key, val in d.items():
child = Element(key)
child.text = str(val)
elem.append(child)
对于elem对象,使用xml.etree.ElementTree 中的tostring() 函数转换成一个字节字符串,然后写入文件
tostring()
tostring(,encoding="unicode")转字符串
print(minidom.parseString(tostring(e)).toprettyxml().replace('<?xml version="1.0" ?>\n',""))换行
设置属性:
e.set('_id','1234')
手动转换:
parts = ['<{}>'.format(tag)]
for key, val in d.items():
parts.append('<{0}>{1}</{0}>'.format(key,val)) # 对于字符‘<’和‘>’,不会被替换成了< 和>而toString()会
# xml.sax.saxutils 中的escape() 和unescape() 函数可以转
parts.append('</{}>'.format(tag))
return ''.join(parts)
缺点:
使用字符串组合构造一个更大的文档并不是那么容易。而Element 实例可以不用考虑解析XML 文本的情况下通过多种方式被处理
特殊字符的处理:
xml.sax.saxutils 中的escape() 和unescape() 函数可以转一些html字符
修改XML
root = doc.getroot() # 所有的修改都是针对父节点元素,将它作为一个列表来处理
root.remove(root.find('sri')) # 调用父节点的remove() 方法从它的直接父节点中删除
root.getchildren().index(root.find('nm'))
e = Element('spam')
e.text = 'This is a test'
root.insert(2, e) # 使用父节点元素的insert() 和append() 方法
doc.write('newpred.xml', xml_declaration=True)
带命名空间的XML文档解析
命名空间:
<html xmlns="http://www.w3.org/1999/xhtml">
这种被放置于元素的开始标签之中,并使用以下的语法:xmlns:namespace-prefix="namespaceURI"
比较:
普通的doc.find('content/html')不能找到标签
需要带上{namespaceURI}才行:doc.find('content/{http://www.w3.org/1999/xhtml}html')
如果要找命名空间下的标签,每个都需要带
print(doc.findtext('content/{http://www.w3.org/1999/xhtml}html/{http://www.w3.org/1999/xhtml}head/{http://www.w3.org/1999/xhtml}title'))
自定义解析函数:
class XMLNamespaces:
def __init__(self, **kwargs):
self.namespaces = {}
for name, uri in kwargs.items():
self.register(name, uri)
def register(self, name, uri):
self.namespaces[name] = '{'+uri+'}'
def __call__(self, path):
return path.format_map(self.namespaces) # 将path进行format,从而不用每次带命名空间
详细解析:
通过iterparse() 函数,注册'start-ns', 'end-ns'事件
如:
for evt, elem in iterparse('ns2.xml', ('end', 'start-ns', 'end-ns')):
建议:
除了要使用到其他高级XML 特性外,还要使用到命名空间,建议你最好是使用lxml 函数库来代替ElementTree 。
例如,lxml对利用DTD 验证文档、更好的XPath 支持和一些其他高级XML 特性等都提供了更好的支持。
十六进制字符串:
binascii模块
binascii.b2a_hex(b'hello')
binascii.a2b_hex(b'68656c6c6f') # 大小写都能处理,如将c换为C也行
base64模块
base64.b16encode(b'hello')
base64.b16decode(b'68656C6C6F') # 只能操作大写形式的十六进制字母
共同点:
在解码十六进制数时,函数b16decode()和a2b_hex()可以接受字节或unicode字符串。
但是,unicode 字符串必须仅仅只包含ASCII 编码的十六进制数。如先b'68656C6C6F'.decode('ascii')再进行解码也行
Base64数据:
仅仅用于面向字节的数据比如字节字符串和字节数组,输出结果总是一个字节字符串
b64encode(bytes)
b64decode()
字节字符串和Unicode 文本都可以作为参数。但是,Unicode字符串只能包含ASCII 字符。
二进制数组数据:
struct模块
struct 模块可被用来编码/解码几乎所有类型的二进制的数据结构。
写示例:
from struct import Struct
record_struct = Struct('<idd')
for r in records:
f.write(record_struct.pack(*r))
增量读取:
record_struct = Struct(format)
chunks = iter(lambda: f.read(record_struct.size), b'') # 利用iter迭代读取
for rec in (record_struct.unpack(chunk) for chunk in chunks):
# process rec
整次读取,然后再分片解析:
data = f.read() # 一次读取
record_struct = Struct(format)
for rec in (record_struct.unpack_from(data, offset) for offset in range(0, len(data), record_struct.size)):
# process rec
方法:
record_struct = Struct('<idd')
字符<指定了字节顺序,表示”低位在前”。> 表示高位在前,或者是! 表示网络字节顺序。
i表示32 位整数
d表示64 位浮点数
idd有三位表示要解包三个数
record_struct.size
包含了结构的字节数
record_struct.pack
record_struct.unpack
record_struct.unpack_from(data, offset)
对于从一个大型二进制数组中提取二进制数据非常有用,因为它不会产生任何的临时对象或者进行内存复制操作。
从offset位置开始直接解包数据
struct.calcsiz()
计算format的结构表示的标准大小
其他:
解包搭配collections 模块中的命名元组对,给返回元组设置属性名称
需要处理大量的二进制数据,最好使用numpy 模块
np.fromfile(f, dtype='<i,<d,<d')
读取嵌套和可变长二进制数据:
struct模块
当读取字节数据的时候,通常在文件开始部分会包含文件头和其他的数据结构。
解包方法:
1.直接解包
file_code, min_x, min_y, max_x, max_y, num_polys = struct.unpack('<iddddi', header)
2.描述符
实现描述符的__get__方法,托管类通过定义多个类属性(描述符)来获取对应的文件头
class StructField:
def __init__(self, format, offset):
self.format = format
self.offset = offset
def __get__(self, instance, cls):
if instance is None:
return self
else:
r = struct.unpack_from(self.format, instance._buffer, self.offset)
return r[0] if len(r) == 1 else r
min_x = StructField('<d', 4)
缺点:
需要使用者指定很多底层的细节(比如重复使用StructField ,指定偏移量等)
3.描述符+类装饰器或元类
class StructureMeta(type):
def __init__(self, clsname, bases, clsdict):
fields = getattr(self, '_fields_', [])
byte_order = ''
offset = 0
for format, fieldname in fields:
if format.startswith(('<','>','!','@')):
byte_order = format[0]
format = format[1:]
format = byte_order + format
setattr(self, fieldname, StructField(format, offset))
offset += struct.calcsize(format) # 通过计算format对应大小来自动指定偏移量
setattr(self, 'struct_size', offset)
class Structure(metaclass=StructureMeta):
def __init__(self, bytedata):
self._buffer = bytedata
@classmethod
def from_file(cls, f):
return cls(f.read(cls.struct_size))
class PolyHeader(Structure):
_fields_ = [
('<i', 'file_code'), # 这样就不用重复使用StructField,而是交给元类
('d', 'min_x'),
('d', 'min_y'),
('d', 'max_x'),
('d', 'max_y'),
('i', 'num_polys')
]
4.辅助描述符以支持嵌套结构
class NestedStruct:
def __init__(self, name, struct_type, offset):
self.name = name
self.struct_type = struct_type
self.offset = offset
def __get__(self, instance, cls):
if instance is None:
return self
else:
# 将原始内存缓冲进行切片操作后实例化给定的结构类型,底层的内存缓冲区是通过一个内存视图初始化的,所以这种切片操作不会引发任何的额外的内存复制
data = instance._buffer[self.offset:self.offset+self.struct_type.struct_size]
result = self.struct_type(data)
setattr(instance, self.name, result) # 为实例instance设置了同名属性值,非数据描述符下次获取时会拿到实例属性
return result # 这里给实例.xx=另一个实例,这样就能实例.xx.yy获取到嵌套字节
class StructureMeta(type):
def __init__(self, clsname, bases, clsdict):
fields = getattr(self, '_fields_', [])
byte_order = ''
offset = 0
for format, fieldname in fields:
if isinstance(format, StructureMeta):
setattr(self, fieldname,NestedStruct(fieldname, format, offset)) # 通过辅助描述符来管理嵌套结构
offset += format.struct_size
else:
if format.startswith(('<','>','!','@')):
byte_order = format[0] # 可以只指定一次byte_order,之后就不用带了
format = format[1:]
format = byte_order + format
setattr(self, fieldname, StructField(format, offset))
offset += struct.calcsize(format)
setattr(self, 'struct_size', offset) # 设置
class Point(Structure):
_fields_ = [('<d', 'x'),('d', 'y')]
class PolyHeader(Structure):
_fields_ = [
('<i', 'file_code'),
(Point, 'min'),
(Point, 'max'), # 通过辅助描述符管理,max.x,max.y进行访问
('i', 'num_polys')
]
f = open('polys.bin', 'rb')
phead = PolyHeader.from_file(f) # 如果通过Point进行访问,就会访问到错误数据,offset不一致
phead.min.x
5.读取变长数据
class SizedRecord:
def __init__(self, bytedata):
self._buffer = memoryview(bytedata) # 通过类来表示每一行的变长字节数据,内存视图
@classmethod
def from_file(cls, f, size_fmt, includes_size=True): # 用于读取一行数据块
sz_nbytes = struct.calcsize(size_fmt) # 计算该行头部格式大小
sz_bytes = f.read(sz_nbytes) # 先读取头部大小
sz, = struct.unpack(size_fmt, sz_bytes) # unpack出头部的信息:代表着该行的变长数据的大小,如多边形的几个边长
buf = f.read(sz - includes_size * sz_nbytes)# 根据总大小-头部大小,得到有多少组边长
return cls(buf) # 返回一个数据块的实例
def iter_as(self, code):
if isinstance(code, str): # 可以传一组边长的格式,如'<dd'
s = struct.Struct(code)
for off in range(0, len(self._buffer), s.size):
yield s.unpack_from(self._buffer, off)
elif isinstance(code, StructureMeta): # 也可以传
size = code.struct_size
for off in range(0, len(self._buffer), size):
data = self._buffer[off:off+size]
yield code(data) # 通过Point实例化获取一行的多组的x,y值
# 之前Point和PolyHeader只是创建了类,并通过描述符获取到了头部信息,接下来才是实例化并通过instance._buffer获取数据块
f = open('polys.bin', 'rb')
phead = PolyHeader.from_file(f) # 读取头部信息,这个from_file执行后read已经到了头部信息之后
polydata = [ SizedRecord.from_file(f, '<i') for n in range(phead.num_polys) ] # 为所有行创建一个类,保存该行数据块
for n, poly in enumerate(polydata):
for p in poly.iter_as('<dd'):
# p是一个数据库的一组边长
for n, poly in enumerate(polydata):
for p in poly.iter_as(Point):
# p是一个Point类实例,p.x, p.y分别是两个描述符,描述符会在__get__方法unpack from(format,_buffer,offset)解析
总结:
基于懒解包的思想。
当一个Structure 实例被创建时,__init__() 仅仅只是创建一个字节数据的内存视图,没有做其他任何事。
特别的,这时候并没有任何的解包或者其他与结构相关的操作发生。
数据的累加与统计操作:
处理一个很大的数据集并需要计算数据总和或其他统计量,用pandas库
字符、字节与编码:
编码码位:
字符的标识,即码位,是0~1 114 111 的数字(十进制),在Unicode 标准中以4~6 个十六进制数字表示,而且加前缀“U+”
字节:
即字符的具体表述,取决于所用的编码。
编码是在码位和字节序列之间转换时使用的算法。
一个相同的字符,码位在unicode标准中的表示是唯一的,但对应不同的编码标准,字节位数不同。
8位(bit)=1字节(Byte)
一个汉字在utf8中3个字节,gbk中2个字节
编解码error:
UnicodeEncodeError:把文本转换成字节序列时,如果目标编码中没有定义某个字符,该文本转换不了就会报错
UnicodeDecodeError:不是每一个字节都包含有效的ASCII 字符,也不是每一个字符序列都是有效的UTF-8 或 UTF-16,使用指定的编码转不了该字节
SyntaxError:使用预期之外的编码加载模块时抛出,解决#coding: utf-8
识别字节编码:
需要先知道字节的编码方式,才能解码,或者靠侦测
统一字符编码侦测包chardet
UTF-8与UTF-16:
UTF-16 编码的序列开头有几个额外的字节b'\xff\xfe',BOM,即字节序标记,指明编码时使用Intel CPU 的小字节序,才不混乱
小字节序:各个码位的最低有效字节在前面
大字节序CPU:编码顺序是相反的
UTF-16 有两个变种:UTF-16LE,显式指明使用小字节序; UTF-16BE,显式指明使用大字节序,不会生成BOM
# 与字节序有关的问题只对一个字(word)占多个字节的编码(如UTF-16 和UTF-32)有影响,而UTF-8生成的字节序列始终一致,因此不需要BOM。
# 某些Windows 应用(尤其是Notepad)依然会在UTF-8 编码的文件中添加BOM;而且,Excel 会根据有没有BOM 确定文件是不是UTF-8 编码,
# 否则,它假设内容使用Windows 代码页(codepage)编码。Python不会
文本编码:
打开时明确传入encoding=参数
open('cafe.txt', 'w', encoding='utf_8') # python2还不支持
编码默认值:
locale.getpreferredencoding() # 最重要的设置,打开文件的默认编码,也是重定向到文件的sys.stdout/stdin/stderr 的默认编码。export LANG="C.UTF-8"可以修改编码
type(my_file)
my_file.encoding # 文本文件默认使用locale.getpreferredencoding()
sys.stdout.isatty() # 是否输出到控制台中
sys.stdout.encoding # 与控制台的编码相同,输出到文件时,如果打开文件时没有指定encoding 参数,默认值由locale.getpreferredencoding() 提供
sys.stdin.isatty()
sys.stdin.encoding # out,in,err的编码规则还可以通过PYTHONIOENCODING="utf-8"环境变量设置
sys.stderr.isatty()
sys.stderr.encoding
sys.getdefaultencoding() # 用于编解码文件名(不是文件内容),如open,设置通过sys.setdefaultencoding('utf8')
sys.getfilesystemencoding()
zipfile解压编码问题:
for fn in f.namelist():
extracted_path = Path(f.extract(fn))
extracted_path.rename(fn.encode('cp437').decode('gbk'))
规范化字符:
Unicode 有组合字符(变音符号和附加到前一个字符上的记号,打印时作为一个整体),有着不同的码位序列
# 虽然是标准等价物,但码位不同,应该规范化
unicodedata.normalize()
-NFC:使用最少的码位构成等价的字符串
# 有些单字符会被规范成另一个单字符。例如,电阻的单位欧姆(Ω)会被规范成希腊字母大写的欧米加。
-NFD:把组合字符分解成基字符和单独的组合字符,如é分解成e和重音符́
-NFKC\NFKD:较严格的规范化形式,对“兼容字符”有影响,各个兼容字符会被替换成一个或多个“兼容分解”字符,会造成格式损失
# unicode为了兼容现有的标准,有些字符会出现多次,即兼容字符
# 只能在特殊情况中使用,例如搜索和索引,而不能用于持久存储,因为这两种转换会导致数据损失。
大小写折叠:
str.casefold()把所有文本变成小写,再做些其他转换
作用:
对于只包含latin1 字符的字符串s,s.casefold() 得到的结果与s.lower() 一样
例外:
1.微符号'μ' 会变成小写的希腊字母“μ”(在多数字体中二者看起来一样);
2.德语Eszett(“sharp s”,ß)会变成“ss”。
3.自Python 3.4 起,str.casefold() 和str.lower() 得到不同结果的有116 个码位。
Unicode文本排序:
locale.setlocale(locale.LC_COLLATE, 'pt_BR.UTF-8')
sorted(fruits, key=locale.strxfrm) # 使用locale.strxfrm 函数
Unicode数据库:
unicodedata.name() # 字符在标准中的官方名称是不是组合字符(如结合波形符构成的变音符号等),
unicodedata.numeric() # 符号对应的人类可读数值(不是码位)
isdecimal()
isnumeric()
支持字符串和字节序列的双模式API:
正则re模块:
如果使用字节序列构建正则表达式,\d 和\w 等模式只能匹配ASCII 字符;
如果是字符串模式,就能匹配ASCII 之外的Unicode 数字或字母。
os模块:
# os模块中的所有函数、文件名或路径名参数既能使用字符串,也能使用字节序列
使用字符串参数调用,该参数会使用sys.getfilesystemencoding() 得到的编解码器自动编码,然后操作系统会使用相同的编解码器解码。
无法使用上述方式自动处理的文件名,可以把字节序列参数传给os 模块中的函数,得到字节序列返回值。
为了便于手动处理字符串或字节序列形式的文件名或路径名,os 模块提供了特殊的编码和解码函数。
fsencode(filename)
fsdecode(filename)
指定列宽:
textwrap 模块
textwrap.fill(s, 70)
textwrap.fill(s, 40, initial_indent=' ') # 开头宽度
textwrap.fill(s, 40, subsequent_indent=' ') # 除首行外宽度
os.get_terminal_size() # 获取终端宽度
模块与包管理:
模块初始化:
概述:
__init__.py
目的是要包含不同运行级别的包的可选的初始化代码,在python3.3后内置,不再强制添加
导入时机:
1. import graphics,文件graphics/__init__.py 将被导入, 建立graphics命名空间的内容。
2. import graphics.format.jpg 这样导入,文件graphics/__init__.py 和文件graphics/formats/__init__.py
将在文件graphics/formats/jpg.py 导入之前导入。
使用:
1. 绝大部分时候让init.py 空着就好
2. 有些情况下可能包含代码,用来自动加载子模块
如:
# graphics/formats/__init__.py
from . import jpg
from . import png
这样的话,仅仅通过import grahpics.formats 来代替import graphics.formats.jpg 以及import graphics.formats.png。
建立formats这个包的命名空间,然后通过formats.jpg来访问
优点:
使用__init__.py 文件来将每部分粘合在一起,将这个包的不同模块导入到这个命名空间,
from .xx.xx import A
这样,用户只需from 包名称 import A即可
3.延迟导入
# __init__.py
def A():
from .a import A # 当调用函数时才导入
return A()
import mymodule
a = mymodule.A() # 导入,并返回
a.spam()
缺点:
继承和类型检查可能会中断,这时的mymodule.A只是一个函数,通过路径访问才行,mymodule.a.A
包命名空间:
概述:
删去用来将组件联合起来的__init__.py文件,直接导入目录,目录下相同的命名空间(原本模块)会以列表形式并存。
包命名空间是一种特殊的封装设计,为合并不同的目录的代码到一个共同的命名空间。
关键:
确保顶级目录中没有__init__.py 文件来作为共同的命名空间
缺失__init__.py 文件使得在导入包的时候会发生有趣的事情:
这并没有产生错误,解释器创建了一个由所有包含匹配包名的目录组成的列表。
特殊的包命名空间模块被创建,只读的目录列表副本被存储在其__path__ 变量中
# 使用相同命名空间的子脚本时,会从这个列表一一寻找
应用:
对于大的框架,这可能是有用的,因为它允许一个框架的部分被单独地安装下载。
它也使人们能够轻松地为这样的框架编写第三方附加组件和其他扩展。
检测:
检查其__file__ 属性,如果没有,那包是个命名空间;
也可以由其字符表现形式中的“namespace”这个词体现出来。
模块导入:
语法:
from xxx import xxx
import的搜索路径:
1.在当前目录下搜索该模块 # 无论python2还是3,都默认有py文件所在目录的路径环境变量。
2.在环境变量 PYTHONPATH 中指定的路径列表中依次搜索
# 可通过命令行export PYTHONPATH=设置,也可通过代码sys.path.append(r"c:\my_library_path")
3.在 Python 安装路径的 lib 库中搜索
添加路径到sys.path:
方式一:
使用PYTHONPATH环境变量
方式二:
创建一个.pth 文件,将目录列举出来,如/some/dir
这个.pth 文件需要放在某个Python 的site-packages 目录,
通常位于/usr/local/lib/python3.3/site-packages 或者~/.local/lib/python3.3/sitepackages。(可由site.getsitepackages()返回list路径确定)
当解释器启动时,.pth 文件里列举出来的存在于文件系统的目录(不存在不会添加!可以相对路径,相对的是pth所在的目录!)将被添加到sys.path。
安装一个.pth 文件可能需要管理员权限,如果它被添加到系统级的Python 解释器。
# 解释器加载后自带的sys.path路径就是从这里来
# site.getsitepackages的路径可通过sitecustomize.py的site.addsitedir('/some/dir/you/want/on/the/path')添加,自定义搜查的路径
# 相对路径的语法暂时不会,可以通过../../来指定
方式三:
sys.path.insert(0, '/some/dir')
问题是将目录名硬编码到了你的源代码。如果你的代码被移到一个新的位置,这会导致维护问题。
推荐使用模块级的变量:
sys.path.insert(0, join(abspath(dirname(__file__)), 'src'))
绝对路径导入和相对路径导入:
绝对导入: 一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块,python3默认
import a.b
from src.plugins import base
# 加载文件夹下的base.py文件
from src import plugins
data_dict = plugins.pack() # pack()
# 导入文件夹(包,类库,模块),会加载文件夹下的init文件,即在包导入时会被首先执行
相对导入:一个模块必须有包结构且只能导入它的顶层模块内部的模块,python2默认,想绝对导入的方法:from __future__ import absolute_import
显示相对导入:显示相对则明确告诉解释器相对于谁来导入。
from .plugins import pack(__init__文件中的方法)
隐式相对导入:隐式相对就是没有告诉解释器相对于谁,但默认相对与当前模块
import moduleY # 官方强烈不推荐
两者比较:
相对导入可以避免硬编码顶层包名,对于包的维护(重命名)是友好的。
(如果你改变了包名,你就必须检查所有文件来修正源码,使移动代码变得困难)
相对导入只适用于在合适的包中的模块。
在顶层的脚本的简单模块中,相对导入将不起作用。
如果包的部分被作为脚本直接执行,那相对导入将不起作用。通过Python的-m 选项来执行先前的脚本才行。
python3 mypackage/A/spam.py ==> python3 -m mypackage.A.spam
绝对导入可以避免与标准库命名的冲突。
导入可见性:
概述:
默认情况下,不会导入所有不以下划线开头的。
配置:
定义了__all__ , 那么只有被列举出的东西会被导出。
__all__ = ['func1', 'func2']
如果__all__ 包含未定义的名字, 在导入时引起AttributeError。
init.py:
没有init.py文件:
同级文件夹内,要运行的py文件导入同级文件
import a
from subpack import a
要运行的py文件导入次级文件
from subpack import a
被导入的文件可以相对导入同级文件
from . import a
有init.py文件:
同级文件夹内
import a
from subpack import a
兄弟文件夹:
from subpack import a
次级文件夹:
反射导入:
import importlib
m_path,clsname = v.rsplit('.',maxsplit=1)
m = importlib.import_module(m_path)
cls = getattr(m,clsname)
相对导入:
importlib.import_module('.b', __package__) # 需要额外的参数,用作解析包名的锚的名称
导入缓存:
模块缓存可以在字典sys.modules 中被找到,通常可以将缓存和模块的创建通过一个步骤完成
sys.modules.setdefault('spam', imp.new_module('spam')) # 如果已经存在,则直接获得已经被创建过的模块
扩展import语句:
方式一:
导入操作被一个位于sys.meta_path 列表中的“元路径”查找器处理
执行一个语句比如import fib 时,解释器会遍历sys.mata_path 中的查找器对象,调用它们的find_module() 方法定位正确的模块加载器。
sys.meta_path.append(Finder())
顺序从头到尾处理
方式二:
sys.path是一个Python 查找模块的目录列表。
在sys.path 中的每一个实体都会被额外的绑定到一个查找器对象上。你可以通过查看sys.path_importer_cache 去看下这些查找器
sys.path_importer_cache 比sys.path 会更大点,因为它会为所有被加载代码的目录记录它们的查找器。
这包括包的子目录,这些通常在sys.path 中是不存在的。
要执行import fib ,会顺序检查sys.path 中的目录。对于每个目录,名称“fib”会被传给相应的sys.path_importer_cache 中的查找器。
这个可以让你创建自己的查找器并在缓存中放入一个实体。
sys.path_importer_cache['debug'] = Finder()
sys.path.insert(0, 'debug')
sys.path_importer_cache 的使用被一个存储在sys.path_hooks 中的函数列表控制。
# sys.path_hooks会顺序检测sys.path,并调用对应的sys.meta_path的查找器,来查找模块
比较:
使用sys.meta_path的导入者可以按照自己的需要自由处理模块
基于路径的钩子只是适用于对sys.path 的处理。通过这种扩展加载的模块跟普通方式加载的特性是一样的。
新创建一个模块对象:
import imp
使用imp.new_module()函数,imp.new_module('spam')
属性:
__name__
__file__ (运行模块加载语句的文件名)
__package__ (包名)
循环引入问题:
A在导入一个py文件B时,在遇到所需要的属性前,又导入了A,循环导入
1.延迟导入
即把import语句写在方法或函数里面,将它的作用域限制在局部。这种方法的缺点就是会有性能问题。
2.代码规范
在代码通过统一接口来互相调用,避免互相直接调用
3.将from xxx import yyy改成import xxx;xxx.yyy来访问的形式
区别:
from A import B
在导入过程中 创建模块对象B 将模块对象的引用保存在本地作用域
也就是说当前作用域有一个名字叫B的对象,locals()可以查看
import A.B
在导入模块的过程 创建模块对象 将模块对象A的引用保存在本地,B要调用还需通过A.B的形式
相同:
简单说,就是import的引用不同,文件还是要从头到尾加载
如果A、B是py脚本:
import多次(from import也一样),只有第一次会执行,后续取缓存;
所以如果先在A中import了B,余下代码还没加载,转到B中 import A时,由于已经import了A,直接忽略了,不会循环引入
如果A、B是py中的某一个属性
先在A所在文件中import了第二个文件的B,A所在文件余下代码还没加载,
转到B所在文件中 import A时,由于已经加载了A所在文件,但由于A代码块还没加载,会报ImportError: cannot import name 'test'
即还没加载到
注:from a.b.c import d
其中a和b两个模块包的init文件只会执行一次,c文件也只会执行一次
import导入so文件:
概述:
一些第三方库如py2上的mysqldb、confluent_kafka,在linux下会import安装路径下的自带so文件,这些so文件依赖一些安装在其他地方的动态库
如果不指定动态库路径,so在import时就会找不到动态库,从而import失败
设置方法:
1.在配置文件/etc/ld.so.conf中指定动态库搜索路径
include /etc/ld.so.conf.d/libc.conf
echo "/usr/local/lib" >> /etc/ld.so.conf.d/libc.conf
ldconfig 搜索出可共享的动态链接库,库文件的格式为:lib***.so.**,进而创建出动态装入程序(ld.so)所需的连接和缓存文件
2.通过环境变量LD_LIBRARY_PATH指定动态库搜索路径。
export LD_LIBRARY_PATH=/usr/local/lib
3.在编译目标代码时指定该程序的动态库搜索路径。
编译代码时,可以对gcc加入链接参数"-Wl,rpath"指定动态库搜索路径
4.代码内设置
①手动导入,这样import时就会cached了
from ctypes import *
lib1 = cdll.LoadLibrary('/home/username/lib/some_library.so')
②os模块重启解释器
if 'LD_LIBRARY_PATH' not in os.environ: # 防止死循环
os.environ['LD_LIBRARY_PATH'] = '/usr/lib/oracle/XX.Y/client64/lib'
os.execv(sys.argv[0], sys.argv)
动态库的搜索路径搜索的先后顺序:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
模块重新加载:
概述:
使用imp.reload() 来重新加载先前加载的模块
原理:
reload() 擦除了模块底层字典的内容,并通过重新执行模块的源代码来刷新它。
模块对象本身的身份保持不变。因此,该操作在程序中所有已经被导入了的地方会自动更新模块。
注意:
通过reload(xx)只会更新import xx这样导入的模块,而from module import name的不会更新
读取位于包中的数据文件:
普通的open()要求绝对文件名,容易混乱,而且包通常安装作为.zip 或.egg 文件,而不是普通的目录,不能open
pkgutil.get_data() 函数是一个读取数据文件的高级工具,不用管包是如何安装以及安装在哪。它只是工作并将文件内容以字节字符串返回给你
如:
pkgutil.get_data(__package__, 'somedata.dat')
第一个参数是包含包名的字符串。你可以直接使用包名,也可以使用特殊的变量,比如__package__
第二个参数是包内文件的相对名称。如果有必要,可以使用标准的Unix 命名规范到不同的目录,只有最后的目录仍然位于包中。
模块安装:
安装私有包:
pip install --user packagename
在sys.path 中用户的“site-packages”目录位于系统的“site-packages”目录之前。因此,你安装在里面的包就比系统已安装的包优先级高
虚拟环境:
方法一:python3.3版本之后
pyvenv 要被创建的目录名 或 python3 -m venv 默认情况下,虚拟环境是空的,不包含任何额外的第三方库
pyvenv --system-site-packages 要被创建的目录名 不会复制,只是可以访问正式环境的包,要想安装,只能pip freeze >requirement.txt
在目录名下,bin文件夹有可执行文件
方法二:
不仅同时支持 python2 和 python3,而且可以为每个虚拟环境指定 python 解释器,并选择不继承基础版本的包。
pip3 install virtualenv
virtualenv -p /usr/bin/python2.7 /home/ubuntu/data/code/model_venv/
--no-site-packages # 无安装的下载模块,默认
--system-site-packages # 能访问外面的安装包,但不像软链,zip打包时不会将外面的第三方库
--always-copy # 复制,貌似没用,还是得pip freeze > requirments.txt 重新安装pip install -r 注意sudo会获取真实环境的安装包
# cp真实环境的安装包到虚拟环境目录下即可
# 复制可能会有c库依赖版本各种问题,还是手动pip安装
--relocatable # 改变pth文件为相对路径,默认就已经是相对的了,无需担心pythonpath的问题
cp -r /usr/local/lib/python2.7/dist-packages/ /home/ubuntu/data/code/model_venv/local/lib/python2.7/dist-packages/
source /home/ubuntu/data/code/model_venv/bin/activate # 进入虚拟环境
deactivate # 退出
如果想多个venv,需要重新执行命令,复制没用,包会共享。
可以先virtualenv创建后复制site-packages和lib-dynload的文件
本地包源:
pypi本地源:
docker run -it --network=host python:3.7.5 bash
pip install pypiserver
mkdir /root/packages 使用 ~/packages 来保存Python包
nohup pypi-server -p 33333 -P /root/.pypipasswd ~/packages 2>1 &
# 还可以使用
密码保护:
pip install passlib
apt-get install -y apache2-utils
htpasswd -c /root/.pypipasswd sam 添加一个新用户名htpasswd /root/.pypipasswd john
pypi-server -P /root/.pypipasswd
使用:
python3 -m twine upload -u apulistech2020 -p apulistech2020 --repository-url http://huawei-infra01.sigsus.cn:33333/repository/pypi/ dist/*
pip install -i http://huawei-infra01.sigsus.cn:33333/ --trusted-host huawei-infra01.sigsus.cn nni
Nexus PyPI源:
docker run -d --network=host --name nexus sonatype/nexus3
访问8081端口
打包工具distutils:
初级使用:
1.编写功能逻辑xxx.py
2.建立setup.py文件
from distutils.core import setup
setup(
name="hello_module", # 包的名字
version="1.0",
author="ljk",
author_email="wilber@sh.com",
py_modules=['hello'], # 打包hello.py文件
packages=['distutils', 'distutils.command'], # 依赖的目录,以模型的形式存在,可以使用package_dir选项来改变这种默认的对应规则
)
3.执行打包命令:
python setup.py sdist
输出到dist目录下
4.其他地方安装:
直接pip
python setup.py install
扩展模块:
对于纯python模块,仅需要列出模块或包,然后Distutils就会去寻找合适的文件,这对于扩展模块来说是不够的,你还需要指定扩展名、源码文件以及其他编译/链接需要的参数(需要包含的目录,需要连接的库等等)
底层的扩展构建机制是由build_ext命令实现的。
ext_modules是Extension实例的列表,每一个Extension实例描述了一个独立的扩展模块。
from distutils.core import setup, Extension
setup(name='foo',
version='1.0',
ext_modules=[Extension('foo', ['foo.c'])], # 最终生成foo.so文件,存放在发布包的根目录中
)
如果一个包下有多个扩展,而且要把这些扩展都放在统一的目录下,则可以使用ext_package关键字
setup(...,
ext_package='pkg',
ext_modules=[Extension('foo', ['src/foo.c']),
Extension('subpkg.bar', ['src/bar.c'])]
)
目前Distutils仅支持C、C++和Objective-C扩展,所以这些源码文件就是C、C++和Objective-C的源码文件。
setup(...,
ext_modules=[Extension('_foo', ['foo.i'],
swig_opts=['-modern', '-I../include'])],
py_modules=['foo'],
)
或者python setup.py build_ext --swig-opts="-modern -I../include"
发布和包的关系:
发布和包有三种关系:它依赖其他包,它服务于其他包,它淘汰其他包。这些关系可以分别用setup函数的参数requires ,provides 和obsoletes 来指定
编译pyd:
加速运行,安全保护
setup(name='foo',
version='1.0',
ext_modules=cythonize([Extension('foo', ['config.py'])]), # 会在 py 文件所在的相应文件夹生成 .c 或者 .cpp 文件(这个取决于Extension() 的 language 参数,language = 'c' 或者 'c++')
)
python setup.py build_ext --inplace
输出:
build目录下:window下生成xxx.obj以及一些库,linux生成xxx.o,里面有编译的一些中间产物
当前路径下会生成:
window下生成xxx.pyd以及一些库,linux生成xxx.so
使用:
就像 pyc 一样正常使用(如果python安装了 PySide 或者 PyQt,可以到它们的目录下看看,它们的主要模块也是 pyd,而且 __init__.py 没有相应的 pyd)
脚本编程与系统管理:
1.通过重定向/管道/文件接受输入
包括将命令行的输出通过管道传递给该脚本、重定向文件到该脚本,或在命令行中传递一个文件名或文件名列表给该脚本。
示例:
import fileinput
with fileinput.input() as f_input: # 上下文使用
for line in f_input:
print(line, end='')
ls | ./filein.py # 管道
./filein.py /etc/passwd # 命令行,文件路径,打开的文件通过f.filename(), f.lineno()访问文件名,文件路径
./filein.py < /etc/passwd # 重定向
2.终止程序并给出错误信息
示例:
raise SystemExit('It failed!') # 会将消息在sys.stderr 中打印,然后程序以状态码1 退出。
也可:
sys.stderr.write('It failed!\n')
raise SystemExit(1)
3.解释命令行选项
可选方法:手动的处理sys.argv 或者使用getopt 模块,optparse库,相比较argparse更先进
argparse 模块可被用来解析命令行选项,配置可以通过python xxx.py -h查看
使用:
1.先创建一个ArgumentParser实例
2.使用add_argument() 方法声明你想要支持的选项。
参数:
dest参数指定解析结果被指派给属性的名字。
metavar参数被用来生成帮助信息。
help生成帮助信息
type 类型,action="store_true"不能使用
action参数指定跟属性对应的处理逻辑
-store用来存储某个值
-append将多个参数值收集到一个列表中
-store_true根据参数是否存在来设置一个Boolean 标志
required该参数至少要有一个
choices会将输入值和可能的选择值做比较,以检测其合法性
default默认值
3.处理sys.argv的值并返回一个结果实例
args = parser.parse()
示例:
import argparse
parser = argparse.ArgumentParser(description='Search some files')
parser.add_argument(dest='filenames',metavar='filename', nargs='*') # 收集所有剩余的命令行参数到一个列表中
parser.add_argument('-p', '--pat',metavar='pattern', required=True,dest='patterns', action='append',help='text pattern to search for')
# -p 和--pat 表示两个参数名形式都可使用。
parser.add_argument('-v', dest='verbose', action='store_true',help='verbose mode')
parser.add_argument('-o', dest='outfile', action='store',help='output file')
parser.add_argument('--speed', dest='speed', action='store',choices={'slow','fast'}, default='slow',help='search speed')
args = parser.parse_args()
# Output the collected arguments
print(args.filenames)
print(args.patterns)
print(args.verbose)
print(args.outfile)
print(args.speed)
4.密码输入提示
getpass模块,有些系统可能不支持getpass() 方法隐藏输入密码,会提前预警
示例:
user = getpass.getuser() # 不会弹出用户名的输入提示,
# 会根据该用户的shell环境或者会依据本地系统的密码库(支持pwd 模块的平台)来使用当前用户的登录名
passwd = getpass.getpass()
想显示的弹出用户名输入提示,使用内置的input 函数:
user = input('Enter your username: ')
5.执行外部命令并获取它的输出
subprocess.check_output(*popenargs, timeout=None, **kwargs)
执行一个指定的命令并将执行结果以一个字节字符串的形式返回,比较简单
参数:
*popenargs:单个字符串或者序列都可以
stderr:默认情况下,check_output() 仅仅返回输入到标准输出的值。
如果你需要同时收集标准输出和错误输出,使用stderr=subprocess.STDOUT参数
timeout:超时时间
shell:默认False,命令的执行不需要使用到底层shell 环境(比如sh、bash)。
一个字符串列表会被传递给一个低级系统命令,比如os.execve() 。
如果你想让命令被一个shell执行,传递一个字符串参数,并设置参数shell=True
适合复杂的shell 命令,比如管道流、I/O 重定向和其他特性
# shlex.quote()函数可以将参数正确的用双引号引起来,可以避免风险
异常:
如果被执行的命令以非零码返回,就会抛出异常
try:
out_bytes = subprocess.check_output(['cmd','arg1','arg2'])
except subprocess.CalledProcessError as e:
out_bytes = e.output # Output generated before error
code = e.returncode # Return code
subprocess.Popen(['wc'],stdout = subprocess.PIPE,stdin = subprocess.PIPE)
对子进程做更复杂的交互,比如给它发送输入
此外,subprocess 模块对于依赖TTY 的外部命令不合适用。例如,你不能使用它来自动化一个用户输入密码的任务(比如一个ssh 会话)。
这时候,你需要使用到第三方模块了,比如基于著名的expect 家族的工具(pexpect 或类似的)
6. 复制、移动文件和目录
shutil 模块有很多便捷的函数可以复制文件和目录
方法:
shutil.copy(src, dst) # cp src dst
shutil.copy2(src, dst) # cp -p src dst,保留元数据信息,如访问时间、创建时间和权限这些基本信息会被保留,
# 但是对于所有者、ACLs、资源fork 和其他更深层次的文件元信息就说不准了,依赖于底层操作系统类型和用户所拥有的访问权限
shutil.copytree(src, dst) # cp -R src dst,复制目录树
shutil.move(src, dst) # mv src dst
参数:
1.对于符号链接而已这些命令处理的是它指向的东西,如果源文件是一个符号链接,那么目标文件将会是符号链接指向的文件
如果只想复制符号链接本身,那么需要指定关键字参数follow_symlinks
shutil.copy(src, dst,follow_symlinks=True)
shutil.copytree(src, dst, symlinks=True) # 保留被复制目录的符号链接
2. copytree()选择性的忽略某些文件或目录
提供一个忽略函数,接受一个目录名和文件名列表作为输入,返回一个忽略的名称列表
def ignore_pyc_files(dirname, filenames):
return [name in filenames if name.endswith('.pyc')]
shutil.copytree(src, dst, ignore=ignore_pyc_files)
内置函数:
shutil.copytree(src, dst, ignore=shutil.ignore_patterns('*~', '*.pyc'))
3.ignore_dangling_symlinks=True
copytree忽略掉无效符号链接
错误处理:
在copytree()复制过程中,函数可能会碰到损坏的符号链接,因为权限无法访问文件的问题等等,
所有碰到的问题会被收集到一个列表中并打包为一个单独的异常,到了最后再抛出。
try:
shutil.copytree(src, dst)
except shutil.Error as e:
for src, dst, msg in e.args[0]: # src源路径,dst目标路径,msg是错误信息
print(dst, src, msg)
7.创建和解压归档文件
也是shutil模块
方法:
make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]])
get_archive_formats() # 获取所有支持的归档格式列表
unpack_archive(filename[, extract_dir[, format]])
示例:
shutil.unpack_archive('Python-3.3.0.tgz')
8. 通过文件名查找文件
os.walk() 函数
示例:
def findfile(start, name):
for relpath, dirs, files in os.walk(start):
if name in files: # 检测name是否在文件名列表中
full_path = os.path.join(start, relpath, name)
print(os.path.normpath(os.path.abspath(full_path))) # 解决奇怪路径问题
relpath:相对于查找目录的相对路径
dirs:一个该目录下的目录名列表
files:那个目录下面的文件名列表
os.path.join连接三个的时候,可能会出现奇怪的路径名比如././foo//bar,可以通过下面方法来解决
os.path.abspath()接受一个路径,可能是相对路径,最后返回绝对路径
os.path.normpath() ,用来返回正常路径,可以解决双斜杆、对目录的多重引用的问题等。
9.读取配置文件
configparser模块能被用来读取配置文件。
配置格式:
[installation]
library=%(prefix)s/lib # %(xx)变量替换,在被读取时进行替换,可以在变量定义前后编写
prefix=/usr/local # prefix: /usr/local也可以
示例:
from configparser import ConfigParser
cfg = ConfigParser()
cfg.read('config.ini') # 读取配置
cfg.sections() # 所有分组,list
cfg.get('installation','library') # 名字不区分大小写
cfg.getboolean('debug','log_errors') # 查找任何可行的值,True可写为true,TRUE,Yes,1
cfg.getint('server','port') # 获取整数
cfg.set('server','port','9000') # 修改内存中的变量
cfg.set('debug','log_errors','False')
cfg.write(sys.stdout) # 写入文件
多个配置文件:
能一次读取多个配置文件然后合并成一个配置,后读取的同分组的变量会覆盖前面已经读取的同分组的相同名字的变量
类似地,变量的改写也会更新所有用到变量替换的地方
10. 日志记录
一般的日志可以通过硬编码logging模块到程序中
配置文件:
import logging.config
logging.config.fileConfig('logconfig.ini')
配置示例:
[loggers]
keys=root # logger的名字
[handlers]
keys=defaultHandler # handler的名字
[formatters] # format的名字
keys=defaultFormatter
[logger_root] # logger_name的logger的详细配置
level=INFO
handlers=defaultHandler
qualname=root
[handler_defaultHandler] # handler_name的handler的详细配置
class=FileHandler
formatter=defaultFormatter
args=('app.log', 'a')
[formatter_defaultFormatter] # formatter_name的format的详细配置
format=%(levelname)s:%(name)s:%(message)s
独立日志功能:
logger = getLogger(__name__),创建一个和调用模块同名的logger模块,该模块下使用自己的logger.info等,这样就能各个模块独立日志
# root根节点的树关系,子logger的日志默认要传递给父节点处理,会一直传递下去直到root。默认logger.propagate=True,这个开始用来控制传递。
# 使用logging都会触发至少增加一个streamhandler
11.限制内存和CPU的使用量
resource模块,只适用Unix系统
方法:
soft, hard = resource.getrlimit(resource.RLIMIT_CPU) # 内存resource.RLIMIT_AS
resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard))
# soft软限制,超过这个值的时候操作系统通常会发送一个信号来限制或通知该进程
# hard硬限制,用来指定软限制能设定的最大值,由系统管理员通过设置系统级参数来决定
拓展:
setrlimit() 函数还能被用来设置子进程数量、打开文件数以及类似系统资源的限制。
示例:
import signal
import resource
import os
def time_exceeded(signo, frame):
print("Time's up!")
raise SystemExit(1)
def set_max_runtime(seconds):
# Install the signal handler and set a resource limit
soft, hard = resource.getrlimit(resource.RLIMIT_CPU)
resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard))
signal.signal(signal.SIGXCPU, time_exceeded) # 注册信号,捕捉CPU超时发出的信号,内存则会抛出MemoryError 异常。
if __name__ == '__main__':
set_max_runtime(15)
while True:
pass
12.启动WEB浏览器
webbrowser 模块能被用来启动一个浏览器,并且与平台无关。
方法:
webbrowser.open('http://www.python.org')
webbrowser.open_new('http://www.python.org') # 新窗口
webbrowser.open_new_tab('http://www.python.org') # 新标签
webbrowser.get() # 获取某个特定浏览器,返回的对象能够使用open,open_new_tab等方法
异常与错误处理:
语法:
try except finally
多个异常:
except (exception1, exception2,。。。。)
异常的引用:
try:
f = open(filename)
except OSError as e: # e即为引用,指向一个被抛出的OSError 异常实例
if e.errno == errno.ENOENT: # 基于异常的状态码来处理
logger.error('File not found')
elif e.errno == errno.EACCES:
.error('Permission denied')
else:
logger.error('Unexpected error: %d', e.errno)
自定义异常:
BaseException 是为系统退出异常而保留的,比如KeyboardInterrupt 或SystemExit 以及其他那些会给应用发送信号而退出的异常。
最好不要继承这个基类来自定义异常,而是Exception类,否则可能会自定义异常不会被捕获而直接发送信号退出程序运行。
示例:
class CustomError(Exception):
def __init__(self, message, status):
super().__init__(message, status) # 是接受所有传递的参数并将它们以元组形式存储在.args 属性中
self.message = message
self.status = status
链接异常:
捕获一个异常后抛出另外一个不同的异常,同时还得在异常回溯中保留两个异常的信息。
def example():
try:
int('N/A')
except ValueError as e:
raise RuntimeError('A parsing error occurred') from e
# The above exception was the direct cause of the following exception:
捕捉最后的异常:
try:
example()
except RuntimeError as e: #
print("It didn't work:", e)
if e.__cause__: # 查看异常对象的__cause__ 属性来跟踪异常链
print('Cause:', e.__cause__)
如果没有from e链接,相当于在except处理异常时又抛出了异常:
def example():
try:
int('N/A')
except ValueError as e:
raise RuntimeError('A parsing error occurred') # 这种情况下不知道异常链到底是内部异常还是某个未知的编程错误
# During handling of the above exception, another exception occurred:
忽略异常链:
def example():
try:
int('N/A')
except ValueError as e:
raise RuntimeError('A parsing error occurred') from None # 会隐藏上一个异常信息
重新抛出被捕获的异常:
try:
int('N/A')
except ValueError:
print("Didn't work")
raise
输出警告信息:
用途:
废弃特性或使用问题等警告信息给用户
方法:
warning.warn()函数
如warnings.warn('logfile argument deprecated', DeprecationWarning)
参数是一个警告消息和一个警告类,
警告消息
警告类有如下几种:UserWarning,DeprecationWarning, SyntaxWarning, RuntimeWarning, ResourceWarning, 或Future-Warning.
运行:
python3 -W all example.py # 输出所有警告消息
python3 -W ignore example.py # 忽略掉所有警告
python3 -W error example.py # 将警告转换成异常
# 在warning.warn()使用前
warnings.simplefilter('always')
warnings.simplefilter('ignore')
warnings.simplefilter('error')
调试基本的程序崩溃错误:
方法一:
python3 -i someprogram.py 可执行简单的调试。-i 选项可让程序异常结束后打开一个交互式shell,然后查看环境
比如查看func函数
在命令行异常后,可以打开Python的调试器 # 适用于-i选项,异常后自动打开shell,保留上次异常的栈信息
import pdb
pdb.pm()
指令:
w:获取追踪信息。
q:退出Python的调试器
方法二:
traceback.print_exc(file=sys.stderr) # 打印栈信息
方法三:
手动启动调试器
pdb.set_trace() # 需要在异常发生前打开
打开调试器后,可以像命令行一样,执行函数
单元测试:
示例:
class TestDict(unittest.TestCase): # 定义一个测试用例的类
def test_init(self):
self.assertEquals(pre_user_level, 111) # 测试用例断言,比较预期结果与实际结果
if __name__ == '__main__':
unittest.main() # 实例化main对应的TestProgram类,执行init初始化,相当于run
断言:
概述:
所有的断言方法都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回。
基本断言方法:
assertEqual(arg1, arg2, msg=None)
assertNotEqual(arg1, arg2, msg=None)
assertTrue(expr, msg=None) # 验证expr是true
assertFalse(expr,msg=None)
assertIs(arg1, arg2, msg=None)
assertIsNot(arg1, arg2, msg=None)
assertIsNone(expr, msg=None)
assertIsNotNone(expr, msg=None)
assertIn(arg1, arg2, msg=None)
assertNotIn(arg1, arg2, msg=None)
assertIsInstance(obj, cls, msg=None)
assertNotIsInstance(obj, cls, msg=None)
比较断言:
assertAlmostEqual (first, second, places = 7, msg = None, delta = None) 验证first约等于second。 palces: 指定精确到小数点后多少位,默认为7
assertNotAlmostEqual (first, second, places, msg, delta) 验证first不约等于second。 palces: 指定精确到小数点后多少位,默认为7
注: 在上述的两个函数中,如果delta指定了值,则first和second之间的差值必须≤delta
assertGreater (first, second, msg = None) 验证first > second,否则fail
assertGreaterEqual (first, second, msg = None) 验证first ≥ second,否则fail
assertLess (first, second, msg = None) 验证first < second,否则fail
assertLessEqual (first, second, msg = None) 验证first ≤ second,否则fail
assertRegexpMatches (text, regexp, msg = None) 验证正则表达式regexp搜索匹配的文本text。 regexp:通常使用re.search()
assertNotRegexpMatches (text, regexp, msg = None) 验证正则表达式regexp搜索不匹配的文本text。 regexp:通常使用re.search()
复杂断言
assertListEqual (list1, list2, msg = None) 验证列表list1、list2相等,不等则fail,同时报错信息返回具体的不同的地方
assertTupleEqual (tuple1, tuple2, msg = None) 验证元组tuple1、tuple2相等,不等则fail,同时报错信息返回具体的不同的地方
assertSetEqual (set1, set2, msg = None) 验证集合set1、set2相等,不等则fail,同时报错信息返回具体的不同的地方
assertDictEqual (expected, actual, msg = None) 验证字典expected、actual相等,不等则fail,同时报错信息返回具体的不同的地方
测试stdout输出:
使用unittest.mock 模块中的patch()函数,将类文件对象StringIO替换sys.stdout,获取StringIO的输出值并与标准输出断言比较
示例:
from io import StringIO
from unittest import TestCase
from unittest.mock import patch
def urlprint(protocol, host, domain):
url = '{}://{}.{}'.format(protocol, host, domain)
print(url)
class TestURLPrint(TestCase):
def test_url_gets_to_stdout(self):
protocol = 'http'
host = 'www'
domain = 'example.com'
expected_url = '{}://{}.{}\n'.format(protocol, host, domain)
with patch('sys.stdout', new=StringIO()) as fake_out: # 上下文的方式使用patch,关闭时自动复原替换的变量
urlprint(protocol, host, domain)
self.assertEqual(fake_out.getvalue(), expected_url) # 断言测试
if __name__=="__main__":
import unittest
unittest.main()
注意:
某些对Python 的C 扩展可能会忽略掉sys.stdout 的配置而直接写入到标准输出中。
在单元测试中给对象打补丁
unittest.mock.patch()可用来打补丁,替换对象,第一个参数为要打打补丁的对象,第二个可选,目标对象,默认为MagicMock实例,可以设置return_value
用法:
1.装饰器
@patch('example.func')
def test1(x, mock_func):
example.func(x) # 装饰后的函数已被替换:<MagicMock name='func' id='2500252104520'>
mock_func.assert_called_with(x) # mock_func==example.func,即函数需要接收额外返回的替换函数
2.上下文管理器
# 装饰器等同于这个形式
with patch('example.func') as mock_func:
example.func(x)
mock_func.assert_called_with(x)
3.单独使用
p = patch('example.func')
mock_func = p.start() # 开始替换
example.func(x)
mock_func.assert_called_with(x)
p.stop() # 复原
多个补丁:
叠加装饰器
叠加上下文管理器
自定义替换值:
x = 42
with patch('__main__.x', 'patched_value'):
print(x)
MagicMock实例:
能够模拟可调用对象和实例,例如mock_func(xx),mock_func.xx都可以。
会记录对象的使用信息并允许你执行断言检查
from unittest.mock import MagicMock
m = MagicMock(return_value = 10) # m()调用时,返回值=10
m(1, 2, debug=True) # 第一次模拟,能传任意参数,同时记录下使用信息以供断言
m.assert_called_with(1, 2, debug=True) # 不报错
m.assert_called_with(1, 2) # 异常
m.upper.return_value = 'HELLO'
m.upper('hello') #
m.upper('world') # 只记录最新的使用信息,断言会根据最新的
assert m.upper.called
m.split.return_value = ['hello', 'world']
m.split('hello world')
m.split.assert_called_with('hello world')
patch中设置MagicMock:
@patch('example.urlopen', return_value=sample_data) # 返回值还是MagicMock实例,只不过有了return值,urlopen()时返回该值
这样设置后,所有位于example 模块中的urlopen() 函数被一个模拟对象替代,该对象会返回一个包含测试数据的ByteIO().
# example.urlopen而不是urllib.request.urlopen
# example使用from urllib.request import urlopen,urlopen() 函数实际上就位于example 模块了。
# 因为第三方库导入后,该对象的全路径就变为example.urlopen了,如果用urllib.request.urlopen,替换的不是example.py中使用的urlopen
在单元测试中测试异常情况:
方法一:
import unittest
def parse_int(s):
return int(s)
class TestConversion(unittest.TestCase):
def test_bad_int(self):
self.assertRaises(ValueError, parse_int, 'N/A')
方法二:
测试异常的具体值:
import errno
class TestIO(unittest.TestCase):
def test_file_not_found(self):
try:
f = open('/file/not/found')
except IOError as e:
self.assertEqual(e.errno, errno.ENOENT) # 比较异常的值
else:
self.fail('IOError not raised')
方法三:
获取异常值:
class TestConversion(unittest.TestCase):
def test_bad_int(self):
self.assertRaisesRegex(ValueError, 'invalid literal .*',parse_int, 'N/A')
可同时测试异常的存在以及通过正则式匹配异常的字符串表示。
assertRaises优点:
与try except Exception捕捉具体的异常相比较,能捕捉除指定异常外的所有情况,如正常情况
上下文管理器使用:
with self.assertRaisesRegex(ValueError, 'invalid literal .*'):
r = parse_int('N/A')
将测试输出用日志记录到文件中:
测试一般用法:
class MyTest(unittest.TestCase):
pass
if name__ == '__main':
unittest.main()
替换为:
def main(out=sys.stderr, verbosity=2):
# 组装一个测试套件
loader = unittest.TestLoader()
# 用来收集测试用例,会为TestCase类扫描某个模块并将其中的测试方法提取出来。
# 更细粒度的控制,可以使用loadTestsFromTestCase() 方法来从某个继承TestCase 的类中提取测试方法
suite = loader.loadTestsFromModule(sys.modules[__name__])
# TextTestRunner 类是一个测试运行类的例子,这个类的主要用途是执行某个测试套件中包含的测试方法。
# 这个类跟执行unittest.main() 函数所使用的测试运行器是一样的。在这里可以进行了一些列底层配置,包括输出文件和提升级别
unittest.TextTestRunner(out,verbosity=verbosity).run(suite) # 重定向输出
if name__ == '__main':
with open('testing.out', 'w') as f:
main(f)
忽略或期望测试失败:
用法:
装饰器
能用来装饰测试类的单个方法,或者装饰整个测试类
示例:
import unittest
import os
import platform
class Tests(unittest.TestCase):
def test_0(self):
self.assertTrue(True) # 正常测试
@unittest.skip('skipped test') # 忽略的测试,返回msg
def test_1(self):
self.fail('should have failed!')
@unittest.skipIf(os.name=='posix', 'Not supported on Unix') # if条件成立则忽略该测试
def test_2(self):
import winreg
@unittest.skipUnless(platform.system() == 'Darwin', 'Mac specific test') # 除非条件成立才进行测试
def test_3(self):
self.assertTrue(True)
@unittest.expectedFailure # 希望失败,无论成功与否测试框架不会打印多余消息,如果成功了,会报unexpected success
def test_4(self):
self.assertEqual(2+2, 5)
性能测试:
方式一:
在Unix系统上简单测试整体花费时间
time python3 someprogram.py
方式二:
详细报告,可以用cProfile模块
python3 -m cProfile someprogram.py
方式三:
具体某个函数的运行性能
写一个时间装饰器,装饰函数即可,@time_cal
time.perf_counter()与time.process_time()
方式四:
具体某代码块的运行性能
@contextmanager
def timeblock(label):
start = time.perf_counter()
try:
yield
finally:
end = time.perf_counter()
print('{} : {}'.format(label, end - start))
with timeblock('counting'):
xxx
方式五:
测试很小的代码片段运行性能,使用timeit 模块会很方便
timeit('math.sqrt(2)', 'import math', number=10000000)