>>> abs(-5.0)
5.0
int(), 直接截去小数部分,剩下整数部分, 返回int型数;
>>> int(5.0) # 5
>>> int(5.1) # 5
>>> int(5.9) # 5
>>> int(6.0) # 6
>>>
>>> int(-5.0) # -5
>>> int(-5.1) # -5
>>> int(-5.9) # -5
>>> int(-6.0) # -6
math.floor() 向下取整,返回int型数;
>>> math.floor(5.0) # 5
>>> math.floor(5.1) # 5
>>> math.floor(5.9) # 5
>>> math.floor(6.0) # 6
>>>
>>> math.floor(-5.0) # -5,小于等于-5.0 的最大整数是-5;
>>> math.floor(-5.1) # -6,小于等于-5.1 的最大整数是-6;
>>> math.floor(-5.9) # -6,小于等于-5.9 的最大整数是-6;
>>> math.floor(-6.0) # -6,小于等于-6.0 的最大整数是-6;
math.ceil() 向上取整,返回int型数;
>>> math.ceil(5.0) # 5
>>> math.ceil(5.1) # 6
>>> math.ceil(5.9) # 6
>>> math.ceil(6.0) # 6
>>>
>>> math.ceil(-5.0) # -5,大于等于-5.0 的最小整数是-5;
>>> math.ceil(-5.1) # -5,大于等于-5.1 的最大整数是-5;
>>> math.ceil(-5.9) # -5,大于等于-5.9 的最大整数是-5;
>>> math.ceil(-6.0) # -6,大于等于-6.0 的最大整数是-6;
round(number[, ndigits]) 四舍五入,如果两边整数一样远,刚返回偶数一边;
参数ndigits表示小数位的数目;如果ndigits=0,返回int型数,如果ndigits>=1, 返回float型数;
>>> round(-2.5) # -2
>>> round(-1.5) # -2
>>> round(-0.5) # 0
>>> round( 0.5) # 0
>>> round( 1.5) # 2
>>> round( 2.5) # 2
>>> round(1.05, 1) # 1.1 本应该靠偶数一边是1.2, 但由于内部存储原因,返回1.1;
#1.05
# + 0.0000_0000_0000_0010
# -----------------------
# = 1.0500_0000_0000_0012
# 所以1.05更靠近1.1 这边;
>>> round(1.15, 1) # 1.1 本应该靠偶数一边是1.2, 但由于内部存储原因,返回1.1;
#1.15
# + 0.0000_0000_0000_0002
# -----------------------
# = 1.1500_0000_0000_0001
# 所以1.05更靠近1.1 这边;
>>> round(1.25, 1) # 1.2
>>> round(1.35, 1) # 1.4
>>> round(1.45, 1) # 1.4
>>> round(1.55, 1) # 1.6
>>> round(1.65, 1) # 1.6
>>> round(2.675, 2) # 2.67, 本应该靠偶数一边是2.68, 但由于内部存储原因,返回2.67;
在python2.7的doc中,round()的最后写着,"Values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done away from 0."保留值将保留到离上一位更近的一端(四舍六入),如果距离两端一样远,则保留到离0远的一边。所以round(0.5)会近似到1,而round(-0.5)会近似到-1。
但是到了python3.5的doc中,文档变成了"values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice."如果距离两边一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2。
round(2.675, 2) 的结果,不论我们从python2还是3来看,结果都应该是2.68的,结果它偏偏是2.67,为什么?这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串1和0后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离2.67要更近一点点,所以保留两位小数时就近似到了2.67。
pow(x, y) # 求x的y次方;
>>> pow(3, 2) # 9,求平方,返回int型数;
>>> pow(3, 1/2) # 1.7320508075688772, 1/2次方即开方运算;
>>> pow(4, 1/2) # 2.0, 平方根返回的是float型数;
math.pow(),与pow()类似
>>> math.pow(3, 2) # 9.0, 返回的是float型数
对于求平方根,除了可以使用math.pow(x, 1/2)外,还有专门的平方根函数math.sqrt(x)
>>> math.sqrt(2) # 1.4142135623730951,求平方根
>>> math.sqrt(4) # 2.0, 平方根返回的是float型数;
三角函数
>>> math.pi # 3.1415_9265_3589_793, 使用math.pi可以调用π;
>>> math.sin(math.pi / 6) # 0.4999_9999_9999_9999_4,得到的并不是0.5;
>>> math.cos(math.pi / 3) # 0.5000_0000_0000_0001,得到的并不是0.5;
>>> math.tan(math.pi / 4) # 0.9999_9999_9999_9999,得到的并不是1.0;
对数,math.log(A, B=e) 以B为底,A的对数,默认以e为底;
>>> math.e # 2.7182_8182_8459_045,自然常数e;
>>> math.log(100, 10) # 2.0,返回的是float型数;
>>> math.log(math.e) # 1.0,
randint(a, b),[a, b]之间的整数,包含b;
>>> import random
>>> random.randint(10, 20) # 16,返回[10, 20]之间的随机整数;算个bug吧
>>> random.randint(20, 10) # 报错ValueError,参数对顺序有要求;
>>> random.randint(10, 10) # 10,上下限相同,返回这个数;
randrange(start, stop=None, step=1, _int=
>>> random.randrange(10, 20, 2) # 返回[10, 20)之间的偶数,不包括stop
uniform(a, b), [a, b]之间的浮点数,包含b;
>>> import random
>>> random.uniform(10, 20) # 12.132xxxxx,返回[10, 20]之间的随机浮点数;
>>> random.uniform(20, 10) # 10.414xxxxx,uniform(),两个参数不关心顺序;
>>> random.uniform(10, 10) # 10.0,上下限相同,返回这个数;
random.random(), [0, 1)之间的浮点数,不包含1;不需要参数
>>> random.random()
0.1862385***
语法:random.choice(seq)
参数:seq可以是列表、元组、字符串
返回:返回一个元素
>>> import random
>>> random.choice([1, 3, 5]) # 从list中随机选择一个
3
>>> random.choice('abc!@#') # 从str中随机选择一个
#
语法:random.sample(seq, n)
参数:seq,列表、元组、字符串;n,数目;
返回:返回一个列表
>>> import random
>>> random.sample([1, 3, 5, 7, 9], 3) # 从list中随机选择三个
[5, 1, 3]
>>> random.sample('abc!@#') # 从str中随机选择三个
['a', '#', 'b']
语法:random.shuffle(x, random=None)
参数:x,一个list;random,??
返回:返回None,传入的lst会被改变
>>> import random
>>> L0 = [1, 2, 3, 4, 5]
>>> random.shuffle(L0) # 将L0洗牌, 该语句返回None
>>> L0
[4, 5, 3, 1, 2]
需要第三方模块 numpy
import numpy as np
f_mean = np.mean(_list) # 平均值
f_var = np.var(_list) # 方差
f_std = np.std(_list, ddof=1) # 标准差
>>> import tkinter
>>> interp tkinter.Tcl()
>>> # 调用tcl语句
>>> interp.eval('source xx.tcl')
>>>
>>> # 调用array names返回str,split后赋值给list
>>> list_a0_names = interp.eval('array names a0').split()
>>>
>>> # 调用llength 返回str,int后赋值给整形变量
>>> i_length = int(interp.eval('llength $list0'))
>>>
>>> # 调用lindex返回str
>>> s_key = interp.eval('lindex $list0 0')
>>> interp.setvar('s_tmp', 'this is a tmp string')
>>> interp.getvar('s_tmp') # 'this is a tmp string'
>>>
>>> s_tmp_var = 's_tmp'
>>> interp.getvar(s_tmp_var) # 'this is a tmp string',使用变量名代替's_tmp'
感觉datetime模块专业些
时间元组: (年,月,日,时,分,秒,周几,年中第几天,是否夏令时),手工指定时,最后3项可以指定为0;
时间戳:指定时间距1970-01-01 00:00:00总秒数,float型数;
>>> import time
>>> time.time() # 1531722744.1899934,float型数
>>> t = (2018,7,16,14,38,59,0,0,0) # 时间元组,最后3项可以指定为0
>>> time.mktime(t) # 1531723139.0,给定时间的时间戳,注意参数只有一个而不是9个
>>> time.strftime('%Y-%m-%d %H:%M:%S') # '2018-07-16 14:54:26',返回str
>>> str = '2018-07-1615:20:00'
>>> time.strptime(str, '%Y-%m-%d %H:%M:%S') # 返回time.struct_time
返回time.struct_time(tm_year=2018, tm_mon=7, tm_mday=16, tm_hour=15, tm_min=20, tm_sec=0, tm_wday=0, tm_yday=197, tm_isdst=-1)
如果时间格式串与字符串不匹配会报告错误:time data xxx does not match format ‘%xxx’
>>> t = (2018,7,16,14,38,59,0,0,0)
>>> time.asctime(t) # 'Mon Jul 16 14:38:59 2018',返回str
>>> time.sleep(1)。
格式 | 说明 |
---|---|
%y | 两位数年份(00-99) |
%Y | 四位数年份(0000-9999) |
%m | 月份(01-12) |
%d | 月内的一天(0-31) |
%H | 24小时制小时数(0-23) |
%I | 12小时制小时数(01-12) |
%M | 分钟数(00=59) |
%S | 秒(00-59) |
%a | 本地简化星期名称 |
%A | 本地完整星期名称 |
%b | 本地简化的月份名称 |
%B | 本地完整的月份名称 |
%c | 本地相应的日期表示和时间表示 |
%j | 年内的一天(001-366) |
%p | 本地A.M.或P.M.的等价符 |
%U | 一年中的星期数(00-53)星期天为星期的开始 |
%w | 星期(0-6),星期天为星期的开始 |
%W | 一年中的星期数(00-53)星期一为星期的开始 |
%x | 本地相应的日期表示 |
%X | 本地相应的时间表示 |
%Z | 当前时区的名称 |
%% | %号本身 |
时间元组: (年,月,日,时,分,秒,微秒),注意这个时间元组有7个元素;
时间戳:给定的时间距离开始时间(1970-01-01 00:00:00)总秒数,float型数;
datetime模块中有一个datetime类,所有的信息都存储在这个类中
>>> import datetime
>>> dt = datetime.datetime # 给datetime类一个别名
>>> type(dt)
#
>>> dt0 = dt(2018,7,16,17,7,30,1)
>>> type(dt0)# 可以从dt0中取想要的数据
#
>>> dt0.year# 取年份, 返回值为int
2018
>>> dt0.month # 取月份, 返回值为int
7
>>> dt0.day# 取日期,返回值为int
16
>>> dt0.hour# 取时,返回值为int
14
>>> dt0.minute# 取分,返回值为int
29
>>> dt0.second# 取秒,返回值为int
20
>>> dt0.microsecond# 取微秒,返回值为int
253127
>>> dt0.date()# 取年月日, 返回datetime.date类
datetime.date(2018,7,16)
>>> dt0.time()# 取时间, 返回datetime.time类
datetime.time(17,7,30,1)
>>> # 取周几:
>>> dt0.weekday()#周一为0, 返回值为int
0
>>> dt0.isoweekday() #周一为1, 返回值为int
1
>>> dt0.isocalendar() # 取日历, 返回tuple,
(2018, 29,1)# 含义:(年,第几周,周几);
>>> dt0.toordinal() # 到公元元年的天数, 返回值为int
736891
>>> dt1 = dt0.fromordinal(54) # 指定到公元元年的天数,算datetime,返回datetime类
>>> dt1
datetime.datetime(1,2,23,0,0)
>>> # 取当前时间:返回datetime类
>>> dt2 = dt.today()
>>> dt2
datetime.datetime(2018,7,17,10,31,53,757945)
>>> dt3 = dt.now()
>>> dt3
datetime.datetime(2018,7,17,10,31,53,758002)
>>>
>>> # 取零时区时间:返回datetime类
>>> dt4 = dt.utcnow()
>>> dt4 # 中国是东8区,所以零时区时间=中国时间-8 (东边日出早,小时数值大)
datetime.datetime(2018,7, 17, 2, 31, 53, 758017)
>>> dt0.isoformat() # 得到时间的标准str,返回值为str
'2018-07-16T17:07:30.1'
>>> dt0.timestamp() # 从datetime取时间戳: 返回float,小数位表示毫秒数
1531732050.000001
>>> dt5 = dt0.fromtimestamp(1531732650.000001) # 从时间戳取datetime: 返回datetime类
>>> dt5
datetime.datetime(2018,7,16,17,17,30,1)
>>> dt.strftime('%Y-%m-%d') # 按自定义格式返回时间str
'2018-07-16'
>>> dt.ctime() # 取英文格式的时间,返回str
'Mon Jul 16 17:07:30 2018'
>>> dt0.timetuple() # 得到9个元素的时间元组,
time.struct_time(tm_year=2018, tm_mon=7,tm_mday=16, tm_hour=17, tm_min=07, tm_sec=30,tm_wday=1, tm_yday=190,tm_isdst=-1)
>>> str='2049-12-31 23:59:59'
>>> dt6 = dt.strptime(str, '%Y-%m-%d %X')
>>> dt6
datetime.datetime(2049,12,31,23,59,59)
>>> now = datetime.datetime.now()
datetime.datetime(2017,8,23,14,21,33,796979)
>>>
>>> now + datetime.timedelta(hours=10)
datetime.datetime(2017,8,24,0,21,33,796979)
>>>
>>> now - datetime.timedelta(days=1)
datetime.datetime(2017,8,22,14,21,33,796979)
>>>
>>> now + datetime.timedelta(days=2, hours=12)
datetime.datetime(2017,8,26,2,21,33,796979)
>>> #使用bin/oct/hex,输入int,返回str;
>>> bin(50) # '0b110010',十进制 -> 二进制,返回的值为字符串,以0b开头;
>>> oct(50) # '0o62',十进制 -> 八进制,返回的值为字符串,以0o开头;
>>> hex(50) # '0x32', 十进制 -> 十六进制,返回的值为字符串,以0x开头;
>>> s_bin = '110010'# 二进制str;
>>> i_int= int(str(s_bin), 2) # 50, 输入二进制str,返回十进制整数;
>>>
>>> i_int = int(str(62), base=8)# 50,输入八进制str,返回十进制整数;
>>> i_int = int(str(32), base=16) # 50,输入十六进制str,返回十进制整数;
缺点是format的参数只能是各进制的整数,不能是str;
所有需要把参数使用int()处理后再使用format, 如:
‘{n:08b}’.format(n=int(s0, base=16))
>>> '{:b}'.format(13) #输入十进制整数,输出二进制str,:b表示转为二进制;
'1101'
>>> '{:08b}'.format(13) #输入十进制整数,输出二进制str,:08b表示转为二进制,宽度为8,不足补0;
'00001101'
>>> '{:08b}'.format(0x000D)#输入十六进制整数,输出二进制str;
'00001101'
>>> s0='0xd'
>>> '{n:08b}'.format(n=int(s0, base=16)) #输入十六进制str, 转为二进制, 中间需要经过int()处理
'00001101'
>>> '{:o}'.format(13) #输入十进制整数,输出八进制str,:o表示转为八进制;
'15'
>>> '{:08o}'.format(13) #输入十进制整数,输出八进制str,:08o表示转为八进制,宽度为8,不足补0;
'00000015'
>>> '{:08b}'.format(0xD)#输入十六进制整数,输出八进制str;
'00000015'
使用大写的:X, 可以转出的16进制字符.
>>> '{:x}'.format(13) #输入十进制整数,输出十六进制str,:x表示转为十六进制
'd'
>>> '{:04x}'.format(13) #输入十进制整数,输出十六进制str,:04x表示转为十六进制,宽度为4,不足补0
'000d'
>>> '{:04x}'.format(0b01101)#输入二进制整数,输出十六进制str;
'000d'
>>> '{:04x}'.format(int('0b01101', base=2)#输入二进制str,输出十六进制str;
'000d'
%的常用转换类型
转换 | 说明 | 示例 |
---|---|---|
d,i | 整型 | ‘%i’%(4.3) 返回’4’ |
u | 无符号整型 | |
f | 浮点型, m.dddd | ‘%5.3f’%(1/3) 返回’0.333’ |
e | 浮点型, m.dddde+/-xx | ‘%5.3e’%(1/3) 返回’3.333e-01’ |
E | 浮点型, m.ddddE+/-xx | ‘%5.3E’%(1/3) 返回’3.333E-01’ |
g | ||
c | char, 单字符 | ‘%c’%(78), 返回’N’ |
s | str或能用str()转换的数据 | ‘%s’%(1/3), 返回’0.3333333333333333’ |
% | 输入一个% | ‘30%%’%(), 返回’30%’ |
>>> n = 5
>>> s = '%8d'%(n)# '5',默认为右对齐;
>>> s = '%08d'%(n)# '00000005',前面补0;
>>> s = '%-8d'%(n)# '5', 使用负号,左对齐;
>>> w = 8
>>> n = 5
>>> s = '%*d'%(w, n)# '5',默认右对齐;
>>> s = '%0*d'%(w, n) # '00000005',前面补0;
>>> s = '%-*d'%(w, n) # '5',左对齐;
只指定总宽度
>>> n = 3.1415926 # 整数部分1位,小数部分7位
>>> '%5f'%(n) # '3.141593' n长度超过5,所以不舍去bit,但为什么最后的6被四舍五入了?
>>> '%6f'%(n) # '3.141593' n长度超过6,所以不舍去bit,但为什么最后的6被四舍五入了?
>>> '%7f'%(n) # '3.141593' n长度超过7,所以不舍去bit,但为什么最后的6被四舍五入了?
>>> '%8f'%(n) # ' 3.141593' 整数部分2bit,小数部分6bit,共8bit;
指定总宽度和小数宽度
>>> n = 3.1415926 # 整数部分1位,小数部分7位
>>> '%5.0f'%(n) # '3',共5bit, 小数部分0bit,整数部分5bit,小数点0bit;
>>> '%5.1f'%(n) # '3.1',共5bit, 小数部分1bit,整数部分3bit,小数点1bit;
>>> '%5.3f'%(n) # '3.142',共5bit, 小数部分3bit,整数部分1bit,小数点1bit;
>>> '%5.4f'%(n) # '3.1416' 小数部分4bit,整数部分1bit,小数点1bit,超过了5bit,实际共6bit,相当于'%6.4f'%(n);
>>> '%5.5f'%(n) # '3.14159' 小数部分5bit,整数部分1bit,小数点1bit,超过了5bit,实际共7bit,相当于'%7.5f'%(n);
>>> '%05.1f'%(n) # '003.1' 小数部分1bit,整数部分3bit,小数点1bit,共5bit;
>>> w_all = 5
>>> w_frac = 1
>>> '%0*.*f'%(w_all, w_frac, n) # '003.1' 小数部分1bit,整数部分3bit,小数点1bit,共5bit;
format是python2.6后增加的功能
>>> '{} and {}'.format('hello', 'world') # 'hello and world' ,不指定位置,按默认顺序;
>>> '{0} and {1} or {0}'.format('hello', 'world') # 'hello and world or hello' ,指定位置,位置可以重复使用;
>>> '{name} is {age} years old'.format(age=18, name='Jim') # 'Jim is 18 years old'
>>> list_info = ['Jim', 18]
>>> '{0[0]} is {0[1]} years old'.format(list_info) # 'Jim is 18 years old'
>>> '{list_arg[0]} is {list_arg[1]} years old'.format(list_arg=list_info) # 'Jim is 18 years old'
语法: {:[填充符][对齐方式][宽度]}
对齐方式:’^‘表示居中,’<‘表示左对齐,’>'表示右对齐;
宽度: 通过数字或变量指定;
>>> '{s0:<10}'.format(s0='abcd') #左对齐
'abcd '
>>> '{s0:^10}'.format(s0='abcd') #居中对齐
' abcd '
>>> '{s0:>10}'.format(s0='abcd') #右对齐
' abcd'
>>> '{s0:0>10}'.format(s0='abcd') #不足补0
'000000abcd'
>>> '{s0:x^10}'.format(s0='abcd') #不足补x
'xxxabcdxxx'
>>> '{s0:x^{w}}'.format(s0='abcd', w=10) #通过变量指定宽度
'xxxabcdxxx'
>>> '{s0:{t}^{w}}'.format(s0='abcd',t='x', w=10) #通过变量指定填充字符, 好像只支持一个字符
'xxxabcdxxx'
>>> '{s0:{t}{d}{w}}'.format(s0='abcd',t='x', d='^', w=10) #通过变量指定对齐方式
'xxxabcdxxx'
out
>>> '{:.3f}'.format(0.123456)# '0.123',.3f表示小数部分占3bit;
>>> '{:7.3f}'.format(0.123456) # '[ ][ ]0.123',7.3f表示小数部分占3bit,字符总宽度7bit;
>>> '{n:{w_all}.{w_frac}f}'.format(m=0.123456, w_all=7, w_frac=3) # '[ ][ ]0.123',通过变量指定宽度;
>>> '{:,}'.format(1234567.89123) # 1,234,567.89123 , ':,'表示添加千分位分隔符;
语法:sort(key=None, reverse=False)
参数:
key,一个函数,作用是指定比较的元素, 通常由lambda指定, 可以指定多个值lambda x: (x[0], x[1], x[2]);
reverse,bool类型,False升序,True降序, 默认升序;
说明:
sort方法会改变list本身, 并返回None, 所以不能这么写a = b.sort();
python3中,sort删除了cmp参数
例子:
>>> L0 = [('a', 1), ('b', 3), ('d', 2), ('c', 1)]
>>>
>>> # 默认排序
>>> L0.sort()
>>> L0
[('a', 1), ('b', 3), ('c', 1), ('d', 2)]
>>>
>>> # key=lambda x:x[1], 按L0元素(是一个tuple)的第1个元素排序,
>>> # 也可以指定key=lambda x:(x[1], x[0])进行多维排序
>>> L0.sort(key=lambda x:x[1])
>>> L0
[('a', 1), ('c', 1), ('d', 2), ('b', 3)]
>>>
>>> # 降序排序
>>> L0.sort(reverse=True) # 默认按升序排序,通过reverse=True按降序排列;
>>> L0
[('d', 2), ('c', 1), ('b', 3), ('a', 1)]
>>>
>>> # sort返回值是None, 即list.sort()没返回任何对象
>>> print(L0.sort())
None
语法:sorted(iterable, key=None, reverse=False)
参数:
iterable: 可迭代对象
key:sorted是一个高阶函数,可以接收一个key函数来自定义排序,key指定的函数作用于list的每一个元素上,并根据key函数的返回值进行排序, 参见list的内置函数sort的key参数.
reverser:bool类型,False升序,True降序; 默认升序.
注意:sorted方法不改变原序列,而是返回一个新的序列;
例1:按绝对值排序、按忽略大小写排序、降序排序
>>> sorted([36,15,-12,9,-21], key=abs) #按绝对值大小排序;
[9, -12, 15, -21, 36]
>>> sorted(['bob', 'About', 'Zoo', 'Credit'], key=str.lower) # 按忽略大小写排序;
['About', 'bob', 'Credit', 'Zoo']
>>> sorted(['bob','About','Zoo','Credit'], key = str.lower, reverse=True) # 按忽略大小写反向排序;
['Zoo', 'Credit', 'bob', 'About']
例2:按字典的key/value排序
>>> d={
}
>>> d['a'] = 3
>>> d['b'] = 4
>>> d['c'] = 2
>>> d['d'] = 1
>>> sorted(d.items(), key=lambda x:x[0]) #按key排序,返回[('a',3),('b',4),('c',2),('d',1)]
>>> sorted(d.items(), key=lambda x:x[1]) #按value排序,返回[('d',1),('c',2),('a',3),('b',4)]
例3:按多条件排序
>>> d0 = {
}
>>> d0['id0'] = {
'score':90, 'name':'name1', 'age':14}
>>> d0['id1'] = {
'score':90, 'name':'name3', 'age':12}
>>> d0['id2'] = {
'score':60, 'name':'name2', 'age':13}
>>> d0['id3'] = {
'score':90, 'name':'name3', 'age':11}
>>>
>>> list_id_sorted = sorted(
... d0.keys(), # 对d0.keys()排序, 所以返回的是id组成的list.
... key=lambda s_id:(
... -d0[s_id]['score'], # 按分数逆序排列,-表示逆序
... d0[s_id]['name'] , # 按姓名顺序排列
... s_id , # 按学号顺序排列
... )
... )
...
>>> for s_id in list_id_sorted:
... print(f'{d0[s_id]['score']} {d0[s_id]['name']} {s_id}')
...
90 name1 id0 # 90分排在前;
90 name3 id1 # 同样90分,按姓名排序;
90 name3 id3 # 同样90分,同样name3,按学号排序;
60 name2 id2 # 60分排在后;
key参数
可以是一个简单的函数, 比如key=str或key=str.lower,
可以是一个通过lambda指定的函数 key=lambda x:(x[0], x[1])
可以是一个自定义的函数:
def sort_str(x):
m = re.match(r'^(\S+?)(\d+)$', x)
if m:
_name = m.group(1)
_idx = int(m.group(2))
else:
_name = x
_idx = 0
return _name, idx # 先按前导字符排序, 后按后缀数字排序
# 这样'a2'排在'a10'前面, 'a*'排在'b0'前面.
L0 = ['a2', 'b3', 'a1', 'b20', 'a0', 'a10', 'a9', 'b0']
L1 = sorted(L0, key=sort_str)
print(L1) # ['a0', 'a1', 'a2', 'a9', 'a10', 'b0', 'b3', 'b20']
使用步骤如下:
# 导入模块
import argparse
# 获取parser, description文本会显示在help信息中
parser = argparse.ArgumentParser(description='args discrip')
# 添加选项, 具体选项类型见后面各节
parser.add_argument('-a')
# 解析选项
args = parser.parse_args(sys.argv[1:])
# 使用选项
print(args.a)
对sys.argv[1:]的说明:
sys.argv # 一个列表,存储命令行参数.
sys.argv[0] # 脚本名.
sys.argv[1:] # 命令行参数.
sys.argv[1][2:] # 截取第一个参数第2bit之后的内容;
# 位置参数可以实现类似于vcs a.v b.v的选项处理,
# 如下例, vfiles是位置参数,不需要使用'-vfile 或者-xx'这种形式指定选项内容;
# default=[] 表示默认参数为空list;
# nargs='*' 表示可以是0个或多个参数;
>>> parser.add_argument('vfiles', type=str,default=[], nargs='*')
_StoreAction(optine_strings=[], dest='vfiles', nargs='*', const=None, default=[], Type=<class 'str'>, choices=None, help=None, metavar=None)
flag类型选项本身无参数, 根据"命令行是否有该选项" 和 “该选项声明值/默认值” 来决定选项实际值(注意, 并不是声明选项, 则该选项为True, 不声明为False, 而是内部可配置):
>>> parser.add_argument(
... '-a', # 选项名称
... action='store_true', # 如果声明-a, 则a=True;
... default=False # 如果不声明(默认)-a, 则a=False;
... )
_StoreTrueAction(optine_strings=['-a'], dest='a', nargs=0, const=True, default=False, Type=None, choices=None, help=None, metavar=None)
>>>
>>> parser.add_argument(
... '-b',
... '--b_long_opt', # b有额外的长选项名称: --b_long_opt
... action='store_false', # 如果声明-b, 则b=False
... default=True, # 如果不声明(默认)-b, 则b=True
... dest='b_new_name' # 内部使用b_new_name取用该选项(而不能使用b)
)
_StoreTrueAction(optine_strings=['-b', '--b_long_opt'], dest='b_new_name', nargs=0, const=False, default=True, Type=None, choices=None, help=None, metavar=None)
注意,不会有以下两种情况,因为这两种情况下,无论选项是否定义,内部得到的值都一样,没有意义;
>>> # action='store_true', default=True,声明选项和默认值都是True
>>> # action='store_false', default=False,声明选项和默认值都是False
通过parse一个内部数组来进行验证,实际使用中是parse真正的命令行数组sys.argv[1:], 注意sys.argv[0]表示的是脚本名称;
测试1:#-a -b都没声明,所以都是default值
>>> lst_args=[]
>>> args = parser.parse_args(lst_args)
>>> args.a # 未声名-a,所以args.a的值为default值False;
False
>>> args.b_new_name # 未声名-b,所以args.b_new_name值为默认值True;
True
>>> args.b # 由于使用了dest, 所以args.b会报错, 只能使用args.b_new_name获取
AttributeError: 'Namespace' object has no sttribute 'b'
测试2:-a -b都声明,所以都是action store值
>>> lst_args=['-ab'] # -a和-b是短选项, 可以合并使用'-ab'表示'-a' '-b'
>>> args = parser.parse_args(lst_args)
>>> args.a # 声名了-a,所以args.a的值为声明值True;
True
>>> args.b_new_name # 声名了-b,所以args.b_new_name值为声明值False;
False
测试3:使用长选项,-a -b都声明,所以都是store值
>>> lst_args=['-a', '--b_long_opt'] # --b_long_opt是长选项, 需要使用双短线('--')进行声明
>>> args = parser.parse_args(lst_args)
>>> args.a # 声名了-a,所以args.a的值为声明值True;
True
>>> args.b_new_name # 声名了-b,所以args.b_new_name值为声明值False;
False
>>> parser.add_argument(
... '-c', # 选项名称
... type=int, # 选项类型是整数
... required=True # 选项是必选选项
)
_StoreAction(optine_strings=['-c'], dest='c', nargs=None, const=None, default=None, Type=<class 'int'>, choices=None, help=None, metavar=None)
>>>
>>> # 选项-c必须声明,否则报错:error:the following argument are required:-c
>>> lst_args=['-c', '3']
>>> args = parser.parse_args(lst_args)
>>>
>>> # 选项-c类型为int,所以str '3'传到args.c时,会转类型为int 3;
>>> print(args.c)
3
-d 为str类型的可选选项,默认值为’default_d’
-d 为str类型的可选选项,默认值为'default_d'
>>> parser.add_argument(
... '-d', # 选项名
... type = str, # 选项类型
... required = False, # '-d'是可选选项
... default = 'default_d' # '-d'不声明时, 默认值为'default_d'
)
_StoreTrueAction(optine_strings=['-d'], dest='d', nargs=None, const=None, default='default_d', Type=<class 'int'>, choices=None, help=None, metavar=None)
>>>
>>> lst_args=['-c', '4', '-d', '5'] # -c 必选, -d 可选
>>> args = parser.parse_args(lst_args)
>>> args.c # 选项-c为必选,类型为int
4
>>> type(args.c)
<class 'int'>
>>>
>>> args.d # 选项-d为可选,类型为str
'5'
>>> type(args.d)
<class 'str'>
>>> parser.add_argument(
... '--e_long_opt' , # 表示长选项
... type=str , #
... required=False , #
... default=[] , #
... nargs='+' , # 表示--e_long_opt这个选项后面可以有多个参数
)
_StoreTrueAction(optine_strings=['--e_long_opt'], dest='e_long_opt', nargs='+', const=None, default=[], Type=<class 'str'>, choices=None, help=None, metavar=None)
>>>
>>> # --e_long_opt 带了两个参数 'opt_e0', 'opt_e1'
>>> lst_args=['-c', '4', '--e_long_opt', 'opt_e0', 'opt_e1']
>>>
>>> args = parser.parse_args(lst_args)
>>>
>>> # --e_long_opt的两个参数组成一个list赋值给args.e_long_opt;
>>> args.e_long_opt
['opt_e0', 'opt_e1']
nargs=’+’ 表示当前选项可以有多个参数, 这些参数组成一个list, 例子见上一节.
如果sys.argv[1:]中出现了"位置选项123" 或 “命名选项–f 456”,但"123" 或"-f"选项没有定义在add_argument()中时,直接使用parser.parse_args()会报告错误:
error: unrecognized arguments: 123 –f 456;
这时需要使用parser.parse_known_args()这个方法(而不是parser.parse_args()这个方法)。
>>> lst_args = ['-a', '123', '-f', '456']
>>> parser.add_argument('-a', action='store_true',default=False)
>>>
>>> args, unknown_args = parser.parse_known_args(lst_args)
>>>
>>> args
Namespace(a=True)
>>> args.a # args得到的值与parser.parse_args()的返回值一致
True
>>>
>>> unknown_args # unknown_args得到的是处理完定义过的args后,剩下的选项;
['123', '-f', '456']
调用命令:
import subprocess
proc=subprocess.Popen(
'ls .vim*', # 待执行的命令
shell=True, # 使用shell
cwd='/home/g00444054', # 到这个目录去执行命令
stderr=subprocess.PIPE,# 执行命令后的报错信息送到pipe(默认打印到stderr)
# 可以通过p.stderr访问该pipe内容;
stdout=subprocess.PIPE,# 执行命令后的输出信息送到pipe(默认打印到stdout)
# 可以通过p.stdout访问该pipe内容
)
等待子进程结束;
使用wait()等待子进程结束,有可能导致死锁,不建议使用wait(), 建议使用communicate()
print(proc.returncode) # 如果为None,表示子进程还未结束
p.wait() # 等待子进程结束,但PIPE太大时它可能导致死锁。
~~print(proc.returncode) # 为0,表示子进程正常退出 ~~
建议使用communicate()等待子进程结束,不会死锁。
print(proc.returncode) # 如果None,表示子进程还未结束
b_stdout, b_stderr = proc.communicate() # 等待子进程结束,返回bytes
print(proc.returncode) # 为0,表示子进程正常退出
查看命令的标准输出:
# b_stdout是二进制str, 需要decode, windows下使用gbk
for s_line in b_stdout.decode('gbk').strip().split('\n'):
print(s_line)
.viminfo
.vimrc
.vim
plugin
syntax
查看命令的标准错误
for s_line in b_stderr.decode('gbk').strip().split('\n')
print(s_line)
#如果cmd改成dir .vimx*,s_line就是:
#找不到文件
另外, 在proc.communicate()前可以通过proc.stdout/proc.stderr来获取stdout和stderr, 但proc.communicate()后会报告io buffer已经关闭了(它们是被当作io对象来访问的).
for b_line in proc.stdout:
print(b_line.decode('gbk').strip())
for b_line in proc.stderr:
print(b_line.decode('gbk').strip())
os.system(cmd)可以得到cmd的返回值(即shell的exit值),而不是命令的输出
>>> value = os.system('ls') #会在Termianl打印命令的输出
file0 dir0
file1 dir1
>>> value #value得到的是命令的返回值,即shell的exit值,而不是命令的输出;
0
os.popen()可以得到命令的输出,返回执行后的信息对象, 通过管道将结果返回;
语法: os.popen(command, [, mode, buffering])
参数1:command :调用的命令;
参数2:mode :返回file对象的模式,默认为"r",只读模式;
参数3:buffering:缓冲区大小,默认-1,无限制;
>>> pipeline = os.popen('ls')
>>> for line in pipeline.readlines():
... print(line.strip())
...
dir0
dir1
file0
file1
Windows对应’nt’,Limux/Unix对应’posix’;
>>> os.name
'posix'
>>> import getpass
>>> getpass.getuser()
'g004440**'
>>> import os
>>> type(os.environ)
<class 'os._Environ'>
>>>
>>> for k, v in os.environ.items(): # 可以把os.environ当作dict访问
... print('{k:<10} = {v}'.format(k=k, v=v))
...
LANG= C
USER= g004440**
HOME= /home/g004440**
...
>>> os.environ['USER'] # 直接通过key查变量值
'g004440**'
>>> os.environ.get('USER') # 使用get()通过key查变量值
'g004440**'
>>> os.environ['CELL_CNT'] # 查不存在的key会报错
KeyError: 'CELL_CNT'
>>> os.environ.get('CELL_CNT', 0) # 通过get()指定返回值,默认为None
0
>>> os.environ['CELL_CNT'] = str(99) # 通过赋值运算修改或添加环境变量,int会报错
>>> os.environ['CELL_CNT'] # CELL_CNT环境变量已经添加
'99'
>>> os.environ.get('CELL_CNT', 0) # 使用get()也可以得到
'99'
注意:通过putenv()设置的环境变量,使用getenv()无法获取,使用os.environ[]也无法获取(即没有修改os.environ的值),它只能是默默地修改了环境变量?
os.getenv(key, default=None) 获取环境变量
os.putenv(name, value, /) 设置环境变量name=value,value必须是str、bytes或os.PathLike obj,不能是int
>>> import os
>>> os.getenv('USER') # 获取环境变量'USER'
'g004440**'
>>> os.getenv('CELL_CNT', 0) # 获取不存在的环境变量,指定返回值
0
>>> os.putenv('CELL_CNT', str(99))
>>> os.getenv('CELL_CNT', 0) # putenv()设置的key,不能使用getenv()获得
0
>>> os.environ.get('CELL_CNT', 0) # os.environ也没有改变
0
dict -> str
>>> import json
>>> d0 = {
1:'a', 2:{
'2k0':'2v0', '2k1':'2v1'}}
>>> s0 = json.dumps(d0) # 将字典转为字符串
>>> s0
'{"1": "a", "2":{"2k0":"2v0", "2k1":"2v1"}}'
>>> type(s0)
<class 'str'>
str -> dict
>>> d1 = json.loads(s0) # 将字符串转为字典
>>> d1
{
1:'a', 2:{
'2k0':'2v0', '2k1':'2v1'}}
>>> type(d1)
<class 'dict'>
注意,json操作文件时,操作对象是文件句柄,而不是文件本身。
dict -> fid
>>> with open('d0.json', 'w') as fid:
... json.dump(d0, fid, indent=4) # 将字典写入到文件中
...
>>> for line in open('d0.json', 'r').readlines():
... print(line)
...
{
"1": "a",
"2": {
"2k0":"2v0",
"2k1":"2v1"
}
}
fid -> dict
>>> with open('d0.json', 'r') as fid:
... d1 = json.load(fid) # 将json文件中加载到字典
...
>>> d1
{
1:'a', 2:{
'2k0':'2v0', '2k1':'2v1'}}
例1: 推荐做法: with … as: 不需要手工关闭文件;
>>> with open('tmp.txt', 'w') as fh: #使用with 打开,使用完fh后,不需要手工关闭之;
... fh.write('line0\n') # 需要手工加换行符
... fh.write('line1\n')
...
例2, 常规做法, 打开文件,写入文件,关闭文件;推荐使用with的方法,不需要手工关闭文件
>>> fh = open('tmp.txt', 'w')
>>> fh.write('line0\n') # 注意不会自动换行,需要手工加换行符;
>>> fh.write('line1\n')
>>> fh.close() # 使用完后需要关闭
例1: 推荐做法, with … as: 不需要手工关闭文件
>>> with open('tmp.txt', 'r') as fh: #使用with 打开文件,不需要手工关闭文件
... for line in fh:
... print(line, end='') # 行尾不加换行, 或者print(line.strip('\n'))
...
输出:
line0
line1
如果待打开的文件中包含中文, 会导致报告UnicodeDecodeError: ‘gbk’ codec can’t decode byte …, 这时, 可以给open加编辑参数: open(‘tmp.txt’, ‘r’, encoding=‘UTF-8’)
(同时获得行号与行文本: for lineno, line in enumerate(fh, 1))
例2: 常规做法:打开文件,读取文件,关闭文件;推荐with的做法
>>> fh = open('tmp.txt') #没有声明打开的模式,默认read模式,即第二个参数默认为r
>>> while True:
... line = fh.readline()#readline,读取一行内容
... if len(line) == 0:#长度等于0表示文件结尾,注意空行的长度为1,换行符占一个长度
... break
... print(line)
...
>>> fh.close()
输出:
line0
<空行> #因为print会默认在结尾加换行符
line1
<空行>
几个read函数
>>> fh.read() #读取整个文件,将之放到一个字符串变量中;
>>> fh.readline() #每次只读取一行,放到字符串中;
>>> fh.readlines() #读取整个文件,将之分析成一个列表;
例3:
>>> with open('tmp.txt', 'r') as fh:
... txt = fh.read() # 'line0\nline1\n',读取所有行,返回str;
例4:
>>> with open('tmp.txt', 'r') as fh:
... txt0 = fh.readline()# 'line0\n',每次读取一行;
... txt1 = fh.readline().strip() # 'line1',每次读取一行, str.strip()可以去行尾换行符和空格;
例5:
>>> with open('tmp.txt', 'r') as fh:
... list_txt = fh.readlines() # ['line0\n', 'line1\n'],读取所有行,放到list中;
使用gzip模块读取gz文件:
import gzip
with gzip.open('file.txt.gz', 'rb') as fid: # 读取模式为rb
for line in fid:
line = line.decode() # 读入的line是二进制格式(b''), 需要解码
line = line.strip('\n') # 去掉换行符
print(line)
模式 | 描述 |
---|---|
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
rb | r的二进制模式 |
wb | w的二进制模式 |
ab | a的二进制模式 |
r+ | r的读写模式(不会创建不存在的文件从顶部开始写会覆盖之前此位置的内容,如果先读后写,则会在文件最后追加内容。) |
w+ | w的读写模式(如果文件存在,则覆盖整个文件,如果不存在,则创建。要close 之后才算完成写入) |
a+ | a的读写模式(可读可写,从文件顶部读取内容,从文件底部添加内容,不存在则创建) |
rb+ | r的二进制+读写模式 |
wb+ | w的二进制+读写模式 |
ab+ | a的二进制+读写模式 |
可以使用with语句代替try…finally语句,好处是语法比较简洁。另见第15.2节。
不管在处理文件过程中是否发生异常,都能保证with语句执行完毕后关闭打开的文件句柄。
>>> with open(file, 'r') as f:
... print(f.read())
#等价于如下写法:
>>> try:
... f = open(file, 'r')
... print(f.read())
... finally:
... if f:
... f.close()
...
1.判断文件/目录是否存在
>>> os.path.exists(test_file) # True | False
>>> os.path.exists(test_dir)# True | False
2.判断文件是目录或是普通文件
>>> os.path.isfile(test_target) # test_target是普通文件时,返回True, "是目录"或"目录不存在"时,返回False;
>>> os.path.isdir(test_target) # test_target是目录时,返回True, "是普通文件"或"文件不存在"时,返回False;
3.判断文件是否可读写
>>> os.access(test_file, os.F_OK) # 文件存在,返回True;
>>> os.access(test_file, os.R_OK) # 文件可读,返回True;
>>> os.access(test_file, os.W_OK) # 文件可写,返回True;
>>> os.access(test_file, os.X_OK) # 文件可执行,返回True;
获取程序所在目录:
#假设py文件 /test/path/a.py的内容为
import os, sys
print(f'os.getcwd() = {os.getcwd()}')
print(f'sys.path[0] = {sys.path[0]}')
#用户在/home/g444054/目录下执行
$ python /test/path/a.py
os.getcwd() = /home/g444054 # 返回的是执行命令的路径
sys.path[0] = /test/path # 返回的是脚本所在的路径
列出指定目录下的文件(指定目录为空时,默认为当前目录),返回列表,注意:返回的list元素不包含对应的path,只是文件名。要递归目录, 需要使用os.walk
>>> os.listdir(path)
['file0', 'file1', 'dir0']
创建目录, 删除目录, 删除文件, 复制文件
>>> os.mkdir(path)# 创建目录;
>>> os.rmdir(path)# 删除目录, 要递归删除使用shutil.rmtree;
>>> os.remove(file) # 删除文件;
>>> os.rename('test.txt', 'test.py') # 重命名
>>> shutil.copy('/home/g004440**/.cshrc', '/home/n004435**/') #复制文件
>>> shutil.copytree('/home/g004440**/dir0', '/home/g004440**/dir.bak') #复制目录和内部所有文件. 目标文件夹不能已经存在.
>>> shutil.rmtree(path) # 删除目录树, 必须指向文件夹, 不能是符号链接.
切换目录
>>> os.chdir(path)# 切换目录
文件大小(字节)
>>> os.path.getsize(filename) #返回文件大小,对目录无效
65525
拆分指定路径: os.path.split(path)(必须有一个参数),返回一个包含两个元素的元组:
[0]: 文件或目录所在的路径,也可以通过os.path.dirname(path)获取
[1]: 目录名或文件名
>>> os.path.split('/home/g004440**/script')
('/home/g004440**', 'script')
>>> os.path.split('/home/g004440**/script/') # 末尾包含'/',全部归到路径
('/home/g004440**/script', '') # 不管script是不是一个目录
>>>
>>> os.path.split('/home')
('/', 'home')
>>> os.path.split('/home/')
('/home', '')
拆分文件扩展名,返回一个元组
>>> os.path.splitext('/home/g004440**/.vim.rc')
('/home/g004440**/.vim', '.rc')
合并路径,如果Linux会用/合并,Win中是\
>>> os.path.join('/home', 'g00444054', 'script') # 合并路径,
'/home/g004440**/script'
>>> os.path.join('/home', 'g00444054', 'script', '')
'/home/g004440**/script/' # 末尾有空元素会多返回一个'/'
>>> os.path.join('', '/home', '', 'g00444054', 'script')
'/home/g004440**/script' # 前面或中间有空元素,不影响结果.
获取路径的绝对路径os.path.abspath(path)或os.path.realpath(path)
>>> os.path.abspath('.')
'/home/g004440**/script'
>>> os.path.abspath('..')
'/home/g004440**'
>>> os.path.abspath('../xxx') # xxx可以不存在。
'/home/g004440**/xxx'
>>> os.path.realpath('../xxx') # xxx可以不存在。
'/home/g004440**/xxx'
目录结构test_walk
test_walk/
|-- dir0
|-- dir1
| |-- dir1.file0
| |-- dir1.file1
|-- dir2
| |-- dir2.dir0
| |-- dir2.dir1
|-- file0
`-- file1
代码
for _s_one_path, _list_dirs, _list_files in os.walk('test_walk'):
print(f'{_s_one_path}/') #test_walk目录中的所有层级目录
for _s_one_dir in _list_dirs: #当前_s_one_path目录中一个层级中的dir
print(f' {_s_one_dir}/')
for _s_one_file in _list_files: #当前_s_one_path目录中一个层级中的file
print(f' {_s_one_file}')
print('')
输出:
test_walk/
dir0/ # test_walk目录中的3个dir
dir1/
dir2/
file0 # test_walk目录中的2个file
file1
test_walk/dir0/ # test_walk/dir0目录中没有dir和file
test_walk/dir1 # test_walk/dir0目录中没有dir, 有2个file
dir1.file0
dir1.file1
test_walk/dir2 # test_walk/dir2目录中有2个dir, 没有file
dir2.dir0/
dir2.dir1/
test_walk/dir2/dir2.dir0 # test_walk/dir2/dir2.dir0目录中没有dir和file
test_walk/dir2/dir2.dir1 # test_walk/dir2/dir2.dir1目录中没有dir和file
一个函数
>>> import random
>>> lst = [random.random() for i in range(10000)]
>>> def f1(lst0):
... lst1 = sorted(lst0)
... lst2 = [i for i in lst1 if i<0.5]
... lst3 = [i*i for i in lst2]
... return lst3
在脚本中测试性能:
>>> import cProfile
>>> cProfile.run('f1(lst)')
7 function calls in 0.005 seconds
Ordered by: standard name
ncalls tottime precall cumtime precall filename:lineno(function)
10.0000.0000.8610.861<stdin>:1(f1)
10.1180.1180.1180.118<stdin>:3(<listcomp>)
10.0780.0780.0780.078<stdin>:4(<listcomp>)
10.0270.0270.8880.888<string>:1(<module>)
10.0000.0000.8880.888{
built-in method builtins...}
10.6650.6650.6650.665{
built-in method builtins...}
10.0000.0000.0000.000{
method 'disable' of ...}
在命令行测试性能:
$ python –m cProfile test.py
ncalls:表示函数调用的次数;
tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间;
percall:(第一个percall)等于 tottime/ncalls;
cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间;
percall:(第二个percall)即函数运行一次的平均时间,等于 cumtime/ncalls;
filename:lineno(function):每个函数调用的具体信息;
sys.path是一个list,内容为python搜索模块的路径;
可以在python程序中修改sys.path: sys.path.append(‘xxx’),但程序退出后这个修改会消失。
如果要永久地修改sys.path,需要设置PYTHONPATH这个环境变量,不同路径使用冒号分开;
>>> import sys
>>> [print(f'"{x}"') for x in sys.path]
""
"/data/XX/code/lib/python"
"/software/xx/lib/python3.6"
sys.stdin.readline()与input()的区别
>>> import sys
>>> s0 = input('Enter string 0: ') # python2中是raw_input()
Enter string 0: abc # 获取用户输入
>>> s0 # input()函数会去掉换行符
'abc'
>>>
>>> s1 = sys.stdin.readline() # 这里不能给str参数,它的参数是str长度;
abc
>>> s1 # 会将换行符一并获取
'abc\n'
>>> s2 = sys.stdin.readline()[:-1] # 切片,去掉最后一个元素
abc
>>> s2 # s2结尾没有换行符,已经去掉;
'abc'
sys.stdout.write()与print()的区别
>>> import sys
>>> print('abc')
abc
>>> n = sys.stdout.write('abc'+'\n') # 函数返回string的长度
abc
>>> n
4
>>> n= sys.stdout.write('abc' + os.lineseq) # 打印后加换行
abc
>>> os.linesep
'\n'
os.linesep获取当前平台的行终止符,str类型,Windows使用'\r\n',Linux使用'\n',Mac使用'\r'
使用sys.stdout.flush(),把写缓冲区的内容冲到标准输出,不然不能实时显示内容
>>> import sys, time
>>> for i in range(5):
... for j in range(5):
... time.sleep(0.5)
... print('*', end='')
... sys.stdout.flush() # flush write buffers,在print后使用
... print('')
...
*****
*****
*****
*****
*****
可以把print的内容输出到标准输出
>>> import sys
>>> print('abc', sys.stderr)
abc <_io.TextIOWrapper name='' mode='w' encoding='ANSI_X3.4-1968'>
Linux安装pip: sudo apt-get install python3-pip
Window安装pip: https://pypi.python.org/pypi/pip#downloads 下载pip-9.x.tar.gz, 解压到目录, 运行python setup.py install
目的 | 操作 |
---|---|
基本安装 | pip3 install pillow |
指定镜像 | pip3 install -i https://pypi.douban.com/simple pillow 下载速度会快 清华镜像 https://pypi.tuna.tsinghua.edu.cn/simple |
参考download和–user选项 |
|
卸载模块 | pip3 uninstall pycryptodome |
升级模块 | pip3 install –upgrade pycryptodome |
检查待更新 | pip3 list –outdated |
查看已安装 | pip3 show –files pycryptodome |
如果在工作站内自己安装模块, 但没有连网, 也没有工作站权限,
pip download -i https://pypi.douban.com/simple PyQt5==5.7.1
注意下载的系统和python版本要与工作站的匹配,
比如在虚拟ubuntu系统python3.6环境下下载, 否则下载到的文件可能不适用.
下载PyQt5==5.7.1时, 会同时下载到PyQt5的whl和它依赖的whl文件.
2. 将下载的whl文件copy到工作站.
3. 在工作站内对用户安装,
–user选项会把模块安装到目录: ~/.local/lib/python3.6/site-packages
pip3 install --user toml-xxxx.whl
pip3 install --user six-xxxx.whl
pip3 install --user pyparsing-xxxx.whl
pip3 install --user packaging-xxxx.whl
pip3 install --user toml-xxxx.whl
pip3 install --user sip-xxxx.whl
pip3 install --user PyQt5-xxxx.whl
pip3 install --user pycrypto-2.6.1.tar.gz
pyinstaller abc.py # 要打包的top代码
-F # 只生成单个的exe文件, 缺点是运行exe时要先解压.
-w # 不生成命令行窗口.
--debug # 生成带debug信息的exe文件.
--clean # 运行前清理编译的临时文件;
--icon # 工具的top图标文件
--distpath # 输出的exe存放的目录
--workpath # 编译的临时文件存放的目录
--hidden-import # 隐含import的模块, 比如自定义/第三方的import模块等
--key # 加密密钥
pyinstaller打包的py文件会转为pyc格式, 但这种文件很容易破解出源码.
操作如下:
pyd是动态链接库文件, 本质上与dll文件相同, 安全性比pyc文件高很多. 所以这种方式不算真正意义上的加密, 而是一个编译过程.
操作如下:
操作如下:
安装或使用过程可能遇到的问题:
C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt\inttypes.h(27): error C2061: 语法错误: 标识符"intmax_t"
......
C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt\inttypes.h(96): error C2143: 语法错误: 缺少"{"(在"__cdecl"的前面)
----------------------------------------
解决方法:
1) 将Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include\stdint.h复制到C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt\stdint.h;
2) 将C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\ucrt\inttypes.h, 第14行, #include 修改为#include "stdint.h"
3) 重新安装PyCrypto.
1) 两个模块全都安装, 加密/运行exe都没问题;
2) 两个模块全都安装, 然后卸载掉PyCrypto, 保留pycryptodome, 加密时报告: Crypto has not attribute '__version__';
3) 两个模块全都安装, 然后卸载掉pycryptodome, 保留PyCrypto, 加密时报告: Crypto has not attribute '__version__';
4) 两个模块全都卸载, 然后单独安装pycryptodome, 加密成功, 但运行生成的exe, 但错误内容与第2)条又不相同:
1. $ ./hardware_debug_tool.exe
2. [27076] Failed to execute script pyiboot01_bootstrap
3. ......
4. zlib.error: Error -3 while decompressing data: incorrect header check
5.
6. During handling of the above exception, another exception occurred:
7.
8. Traceback (most recent call last):
9. ......
10. ImportError: Loader FrozenImporter cannot handle module os
解决方法:
加密效果如这个网址上说的https://blog.csdn.net/mutex86/article/details/45367143:
没办法直接生成文件的md5,只能通过读取文件,用文件内容生成md5
>>> import hashlib
>>> def genFileMd5(filename):
... md5 = hashlib.md5()
... with open(filename, 'rb') as fid:
... while True:
... b = fid.read(8096)
... if not b:
... break
... md5.update(b)
... s_md5=md5.hexdigest()
... return s_md5
...
>>> s_md5 = genFileMd5('/home/g004440**/.cshrc')
与Linux命令 md5sub filename得到的结果一致;
self.__class__.__name__ # class name
sys._getframe().f_code.co_filename # 当前文件名, 或通过__file__获取
sys._getframe().f_code.co_name # 当前函数名
sys._getframe().f_lineno # 当前行号
import sys
class Foo(object):
def print_name(self):
print(f'FileName: {__file__}')
print(f'FileName: {sys._getframe().f_code.co_filename}')
print(f'ClsName : {self.__class__.__name__}')
print(f'Method : {sys._getframe().f_code.co_name}')
print(f'Lineno : {sys._getframe().f_lineno}')
f0 = Foo()
f0.print_name()
输出:
FileName: test.py
FileName: test.py
ClsName : Foo
Method : print_name
Lineno : 9
参数:map()函数接收两个参数:一个函数、一个Iterable序列。
功能:将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回, map()主要是把运算规则抽象化;
返回值:map object,可以通过list(map object)将之转为list
例1:
>>> def f(x):
... return x*x
...
>>> r = map(f, [1,2,3])
>>> r
<map object at 0x2b*******>
>>> list(r) # r是一个Iterator,使用list()函数把整个序列计算出来
[1, 4, 9]
例2:
>>> list(map(str, range(3))) # 把列表的int转为str;
['0', '1', '2']
参数:reduce()函数接收三个参数:一个函数、一个Iterable序列,还有一个初始值。
功能:将传入的函数作用在序列上,把函数计算结果一直和序列的下一个元素做累积计算;
reduce(f, [x0, x1, x2, x3]) 等价于 f(f(f(x0, x1), x2), x3)
>>> import functools
>>> def add(x,y):
... return x+y
...
>>> functools.reduce(add, [1,3,5,7,9]) # 将list元素累加
25
>>> functools.reduce(add, [1,3,5,7,9], 100) #将list累加,初始值为100
125
参数:filter()函数接收两个参数:一个函数,一个序列;与map类似。
功能:把传入的函数依次作用于每个元素,根据函数返回值是True或False决定元素是否保留;
返回值:filter object
>>> def is_odd(n):
... return n%2==1
...
>>> r = filter(is_odd, range(9))
>>> r
<filter object at 0x2b******>
>>> list(r)
[1,3,5,7]
语法:zip(iterable[, iterable, …])
作用:将参数中对应的元素打包成一个个元组;
返回值:返回元组组成的对象(可以节约内存),可以使用list()将之转为列表;
参数:iterable,一个或多个迭代器
例1
>>> lst0 = [1, 2, 3]
>>> lst1 = [4, 5, 6]
>>> lst2 = [7, 8, 9, 10, 11]
zip返回的是zip对象,可以用list(),将之转为列表
>>> zip_0 = zip(lst0, lst1)
>>> zip_0 # 是一个对象
<zip object at 0x2ad*******>
>>> list(zip_0) #通过list()把它转为列表
[(1, 4), (2, 5), (3, 6)]
如果两个iterable不等长,取最短的
>>> list(zip(lst0, lst2)) # 如果两个iterable不等长,取最短的
[(1, 7), (2, 8), (3, 9)]
如果iterable只有一个,则元组元素就只有一个
>>> list(zip(lst0)) # 如果iterable只有一个,则元组元素就只有一个
[(1,), (2,), (3,)]
如果iterable有3个,则元组元素就有3个
>>> list(zip(lst0, lst1, lst2)) #如果iterable有3个,则元组元素就有3个
>>> [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
zip(*XX),将zip对象XX做解压,返回list
>>> a0, a1 = zip(*[(1, 4), (2, 5), (3, 6)])
>>> a0
(1, 2, 3)
>>> a1
(4, 5, 6)
语法:
?range(stop)
?range(start, stop[, step])
参数:
?start:起始值,可选,默认为0;
?stop:结束值,必选;注意:结束值不包含在返回的object中;
?step:步长,可选,默认为1;
返回值:class ‘range’
range(i, j) 包含:i, i+1, i+2, …, j-1;
>>> range(3)
range(0, 3)
>>> type(range(3))
<class 'range'>
>>> list(range(3)) # 不包含3
[0, 1, 2]
返回一个对象的序号,转成16进制后即内存地址。
>>> class Test():
...pass
...
>>> t0 = CTest()
>>> t0
<__main__.CTest object at 0x00328D710>
>>> id(t0)
53008144
>>> '{:x}'.format(id(t0)) # id()返回的为10进制数,转为16进制后为内存地址;
328d710
exec执行字符串形式的代码(python语句,做某事),返回None
>>> exec('a = 6') # 相当于把a=6这条语句放在此处执行
>>> a
6
eval执行字符串形式的表达式,返回表达式的值
>>> a = eval('2*3')
>>> a
6
>>> b = eval('a==6')
>>> b
True
物理行:在编写程序时所"看见"的行。
逻辑行:Python"看见"的单个语句。
Python假定每个物理行对应一个逻辑行。
可以使用分号把多个逻辑行写到一个物理行中;
可以使用续行符在多行物理行中写一个逻辑行;
还有一个暗示的行连接:如果物理行使用了括号(圆括号、方括号或花括号),且括号没有闭合,则认为后面的行仍然是同一个逻辑行。
ord() 将char转为Unicode(065535)值(在python2中是ASCII(0255)值)
>>> ord('a')
97
>>> ord('\n')
10
chr() 将Unicode(065535)值(在python2中是ASCII(0255)值)转为char
>>> chr(97)
'a'
>>> chr(10)
'\n'
unichr() 在python3中去掉,由chr代替。
通过if … else实现
>>> Z = I0 if S==False else I1
通过and … or 实现
>>> def sel(S, I0, I1):
...return (S and [I1] or [I0])[0]
>>> import os
>>> os.get_terminal_size()
os.terminal_size(columns=111, lines=33)
>>> os.get_terminal_size().columns
111 # int, 得到的是窗口列占的字符数
>>> os.get_terminal_size().lines
33 # int, 得到的是窗口行占的字符数
import re
说明:
>>> re.match(r’a’, ‘abc’) # 匹配,因为a出现在了str的起始位置
>>>
>>> test = ‘abcd’
>>> if re.match(r'bc', test):
... print('ok')
... else:
... print('failed')
...
failed # bc没出现在test开头,所以匹配不成功;如果需要从非开始位置匹配,需要使用re.search.
说明:
>>> re.search(r’c\w+’, ‘abcde’) # 匹配,span=(2,5);即开始匹配的char idx为2,结束匹配的char idx为4;注意不包含char idx 5;
>>> re.search(r’c’, ‘abcdec’) # 匹配第一次出现的c,span=(2,3);注意:不会匹配第二次出现的c。
说明:搜索string,返回列表,列表内容为全部能匹配的子串或捕获括号内的字符;
没有捕获括号时,列表内容为匹配的子串
>>> f0 = re.findall(r'\d+', 'a00b01c02d03')
>>> f0 # 返回列表,注意:由于没有捕获括号,所以列表内容为匹配的子串
['00', '01', '02', '03']
pattern中有捕获括号,则findall返回的列表中是括号捕获的内容
>>> f0 = re.findall(r'12(3|4)?', 'a12b123c124d12') # pattern中有捕获括号;
>>> f0 # 返回列表,注意:由于pattern中有捕获括号,则findall返回的列表中是括号捕获的内容;
['', '3', '4', '']
如果需要返回pattern匹配的内容,需要使用非捕获括号
>>> f0 = re.findall(r'12(?:3|4)?', 'a12b123c124d12') # pattern中括号使用了?:,为非捕获括号;
>>> f0 #返回列表,注意:由于pattern中括号是非捕获模式,则findall返回的列表内容为匹配的子串;
['12', '123', '124', '12']
说明:搜索string,返回迭代器,迭代器可以顺序访问每一个匹配结果(即一个match对象);
>>> # f0是一个
>>> f0 = re.finditer(r'12(?:3|4)?', 'a12b123c124d12')
>>> for e in f0:
... print(e.group(0)) # 注意,e是一个match对象。
...
12
123
124
12
用正则表达式切分字符串,返回切分后的列表;
比使用固定字符(str.split(‘ ’))更灵活;
re.split(r’[\s\,\;]+’, ‘a, b; ; cd’) # [‘a’,’b’,’c’,’d’]
参数说明:
pattern,表示要匹配的pattern
repl,表示要将pattern替换成的目标字符串
string,原字符串
count,替换的个数
flags,是个什么东西?
基本替换:
>>> s0 = ‘hello 123 world 456’
>>> s1 = re.sub(r‘\d+’, ‘222’, s0) # 将s0字符串中的数字串替换为‘222’;
‘hello 222 world 222’
>>> s0 = 'come dog, go dog'
>>> s1 = re.sub(r'come (\w+), go \1', '\g<1>', s0) # 返回‘dog’
'dog'
# 说明:\1 是pattern中对group(1)的逆向引用;
# 说明:\g<1>,是repl中对pattern group(1)的逆向引用;
例2:
>>> s2 = re.sub(r’come (?P<name>\w+), go (?P=name)’, ‘\g<name>’, s0)
# 返回‘dog’,使用命名捕获,效果同s1
# 说明:(?Pxxx),是pattern中定义的命名组,name为组名,即原来的group(1)。
# 说明: (?P=name),是pattern中对命名组(name)的逆向引用。
# 说明:\g,是repl中对命名组的逆向引用。
>>> def _add1(m0):# 函数的参数是match对象
... str_new = ''
... for char in m0.group(‘num’): # match对象匹配的命名组(num)是字串,对它做循环
... #字符转为整数,然后加1,再转回字串,取最低位
... str_new += str(int(char)+1)[-1]
... return str_new
...
>>> s0 = ‘hello 123, world 456 696’
>>> s1 = re.sub(r’(?P<num>\d+)’, _add1, s0)
'hello 234, world 567 707' # s0中的数字每一位都加1, 9+1后取0.
>>> #接上一个例子,要求只替换两次,第三个匹配不替换:
>>> s1 = re.sub(r’(?P<num>\d+)’, _add1, s0, 2)
'hello 234, world 567 696' # s0中的数字每一位都加1, 但第3个匹配不替换.
re.compile()用于编译正则表达式模式字符串,并生成Regular Expression Objects;
如果正则表达式要重复使用几千次,可以预先编译该正则表达式,后续重复使用时就不需要编译了;
Regular Expression Objects可以使用上述的re的方法(match、search、findall等)。
>>> c0 = re.compile(r’(\d+)-(\d+)’) # 编译时不需要给匹配字符串;
>>> c0.match(‘010-123456’) # 返回match对象。调用时不需要给正则串;
>>> c0.match(‘010-123456’).group(2) # ‘123456’ , 第二个括号截获的内容;
>>> c0.match(‘abc010-123456def’) # None,使用match无法匹配,需要使用search。
>>>
>>> c0.search(‘abc010-123456def’) # 返回search对象。
>>> c0.search(‘abc010-123456def’).span(2) # (7, 13),第2个括号截获的子串的索引;
match、search等方法,有一个参数是flags,作用改变匹配的方式。
可用flags如下:
Flag值 | 作用 |
---|---|
0 | 默认值为0,不改变匹配方式 |
re.I(IGNORECASE) | 忽略大小写 |
re.M(MULTILINE) | 多行模式,改变^和$的行为 |
re.S(DOTALL) | 点可以匹配任意字符,包括换行符 |
re.L(LOCALE) | 做本地化识别的匹配,不推荐使用?? |
re.U(UNICODE) | 使用\w\W\s\S\d\D使用取决于unicode的定义的字符属性, python3默认使用该flag |
re.X(VERBOSE) | 冗长模式,该模式下patter字符串中可以是多行的,忽略其中的空白字符,且可以在其中添加注释 |
例子:
>>> re.match(r’a’, ‘A’, flags=0) # 正常匹配方式,大小写不同,不匹配,返回None;
>>> re.match(r’a’, ‘A’, flags=re.I) # 忽略大小写匹配,返回match对象;
>>> re.match(r’a B’, ‘AB’, flags=re.I) # patter中有空格,不匹配,返回None;
>>> re.match(r’a B’, ‘AB’, flags=re.I|re.X) # 忽略patter中的空白,匹配,返回match对象;
match对象通过调用这些方法,可以处理match匹配的内容。
作用:获取一个或多个分组截获的字符串;
例:
>>> m0 = re.match(r’^(\d+)-(\d+)’, ‘010-12345abc’) # 返回一个match对象
>>> m0.group(0) # ‘010-12345’,正则式匹配到的所有内容;
>>> m0.group(1) # ‘010’ ,第一个括号提取的内容;
>>> m0.group(2) # ‘12345’ ,第二个括号提取的内容;
>>> m0.group(1, 2) # (‘010’, ‘12345’) ,前2个括号提取的内容组成的元组;
>>> m0.group(3) # 报错:IndexError: no such group;
作用:以元组形式返回分部分组截获的字符串,相当于调用group(1,2, … last); default表示没有截获字符串的组以这个值替代,默认为None。
例:
>>> m0 = re.match(r’^(\d+)-(\d+)’, ‘010-12345abc’) # 返回一个match对象
>>> m0.groups() # (‘010’, ‘12345’),group(1)和group(2)组成的元组
作用:返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上;
例:暂无
作用:
>>> m0 = re.search(r’(\d+)-(\d+)’, ‘abc010-123456def’)
>>> m0.group(0) # ‘010-123456’,正则式匹配到的内容;
>>> m0.start() # 3,group0截获的子串的起始idx;
>>> m0.end() # 13,group0截获的子串的结束idx+1;
>>> m0.span() # (3, 13),即(m0.start(),m0.end());
>>> m0.group(1) # ‘010’,第一个括号截取到的内容;
>>> m0.start(1) # 3,group1截获的子串的起始idx;
>>> m0.end(1) # 6,group1截获的子串的结束idx+1;
>>> m0.span(1) # (3, 6),即(m0.start(1), m0.end(1));
>>> m0.group(2) # ‘123456’,第2个括号截取到的内容;
>>> m0.start(2) # 7,group1截获的子串的起始idx;
>>> m0.end(2) # 13,group1截获的子串的结束idx+1;
>>> m0.span(2) # (7, 13),即(m0.start(2), m0.end(2));
>>> m0.group(3) # 报错,IndexError: no such group;
>>> m0.start(3) # 报错,IndexError: no such group;
>>> m0.end(3) # 报错,IndexError: no such group;
>>> m0.span(3) # 报错,IndexError: no such group;
作用:将匹配到的分组代入template中然后返回。template中可以使用\id或\g、\g引用分组,但不能使用编号0。\id与\g是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g<1>0。
例:
>>> m0 = re.search(r’(\d+)-(\d+)’, ‘abc010-123456def’)
>>> m0.expand(r’\2:\1’) # ‘123456:010’,将group()中的元素重新组合为:’grp2:grp1’
可以逐个列出:[abc]
可以给出范围:[a-c]
可以使用定义字符集:[\w]
可以取反:[^abc],不是abc的其它字符
特殊字符在字符集中要转义:[^ - [ ] ]
字符集 | 说明 |
---|---|
\d | 数字,等价于[0-9]; |
\D | 非数字,等价于[^\d]; |
\s | 空白字符,等价于[<空格>\t\r\n\f\v]; |
\S | 非空白字符,等价于[^\s]; |
\w | 单词字符,等价于[_A-Za-z0-9]; |
\W | 非单词字符,等价于[^\w]; |
表示匹配字符重复的次数。
数量词 | 说明 |
---|---|
* | 匹配前一个字符0~∞次; |
+ | 匹配前一个字符1~∞次; |
? | 匹配前一个字符0~1次; |
{m} | 匹配前一个字符m次; |
{m,n} | 匹配前一个字符m~n次; |
注意:量词可以作用在多个字符上,如:
>>> re.search(r’(123)+’, ‘a1123123123b’).group(0) # ‘123123123’,截取’123’重复多次;
>>> re.search(r’(123){
2}’, ‘a1123123123b’).group(0) # ‘123123’,截取’123’重复2次;
正则默认为贪婪匹配,进行非贪婪匹配需要使用?;
例1:
>>> re.search(r’a(\d*)’, ‘a1234b’).group(1) # ‘1234’,贪婪匹配,截取第一次出现的‘a+所有数字’;
>>> re.search(r’a(\d*?)’, ‘a1234b’).group(1) # ‘’,非贪婪匹配,截取第一次出现的‘a+第0个数字’;
>>> re.search(r’(\d*)’, ‘a1234b’).group(1) # ‘’,贪婪匹配,截取第一次出现的‘所有数字’;注意:这里用的\d*,会在idx==0时匹配上0个数字,所以无法截取到后面的’1234’。
例2
>>> re.search(r’(\d+)’, ‘a1234b’).group(1) # ‘1234’,贪婪匹配,截取第一次出现的所有数字;
>>> re.search(r’(\d+?)’, ‘a1234b’).group(1) # ‘1’,非贪婪匹配,截取第一次出现的第一个数字;
例3
>>> re.search(r’a(\d?)’, ‘a1234b’).group(1) # ‘1’,贪婪匹配,截取第一次出现的1个数字;
>>> re.search(r’(\d??)’, ‘a1234b’).group(1) # ‘’,非贪婪匹配,截取第一次出现的0个数字;
例4
>>> re.search(r’a(\d{
1,3})’, ‘a1234b’).group(1) # ‘123’,贪婪匹配,截取第一次出现的n个数字;
>>> re.search(r’(\d{
1,3}?)’, ‘a1234b’).group(1) # ‘1’,非贪婪匹配,截取第一次出现的1个数字;
只有“或”逻辑,没有“与”逻辑;
例1:不使用括号时,| 两边的字符做为整体来匹配,左边和右边出现任意一个即可。
>>> re.search(r’abc|def’, ‘abc’).group(0) # ‘abc’
>>> re.search(r’abc|def’, ‘def’).group(0) # ‘def’
>>> re.search(r’abc|def’, ‘abdef’).group(0) # ‘def’
例2:使用括号时,| 只作用到括号内,括号内左边和右边出现任意一个即可。
>>> re.search(r’ab(c|d)ef’, ‘abcef’).group(0) # ‘abcef’
>>> re.search(r’ab(c|d)ef’, ‘abdef’).group(0) # ‘abdef’
说明:
例:
re.match(r’(\d)abc\1’, ‘9abc9’).group(1) # 返回9,普通分组,匹配成功。
在正则中使用\1向前引用,在match对象中使用group(1)访问;
re.match(r’(?P<idx>\d)abc(?P=idx)’, ‘9abc9’).group(‘idx’) # 返回9, 命名分组,成功。
在正则中使用(?P=idx)向前引用,在match对象中使用group(‘idx’)访问;
re.match(r’(?P<idx>\d)abc\1’, ‘9abc9’).group(1) # 同上,\1和group(1)仍然可以使用。
re.match(r’(?P<idx>\d)abc(?P=idx)’, ‘9abc9’).group(2) # IndexError, no such group。
注意:向前引用(?P=idx) 中有括号,但并不占据分组group(2);
s1 = re.sub(r'come (\w+), go \1', '\g<1>', ‘come dog, go dog’) # 返回dog
特殊构造 | 说明 |
---|---|
(?:…) | (…)的不分组/不捕获版本,这个地方的左括号不计入编号; 可以用于把多个字符做为一个整体使用‘|’或使用数量词,同时不影响其它分组的编号。如: re.match(r’(?:abc){2}(\d)’, ‘abcabc9’).group(1) # 返回9,abc的括号不占用group(1)。 |
(?iLmsux) | 用在pattern开头,用于指定匹配模式,可以使用iLmsux中的一个或多个; 如:re.match(r’(?i)(abc)’, ‘AbC’).group(1) # 返回AbC,pattern开头的(?i)表示忽略大小写;更多匹配模式见第9.2节。 |
(?#…) | #后的内容做为注释被忽略;如: re.match(r’abc(?#ignore)(123)’, ‘abc123’).group(1) # 返回123,(?#ignore)被忽略; |
x(?=…) | x之后必须有…才能匹配成功,但…不消耗字符串内容。如:re.match(r’a(?=\d)(\w+)’, ‘a1bc’).group(1) #返回1bc,a(?=\d)表示a后面要跟数字,但这个数字并没有在这里被匹配,而是被后面的(\w+)匹配,并放到了group(1)中。 |
x(?!..) | x之后必须没有…才能匹配成功,但…不消耗字符串内容。 |
(?<=…)x | x之前必须有…才能匹配成功,但…不消耗字符串内容。 |
(? | x之前必须没有…才能匹配成功,但…不消耗字符串内容。 |
(?(id/name)yes-patt|no-patt) | 如果group(id/name)匹配到字符,则需要匹配yes_pattern,否则需要匹配no_pattern,|no_pattern可以省略。 |
>>> import re
>>> a = ‘1234’
>>> b = ‘1234567’
>>> re.match(f’{
a}’, b)
<_sre.SRE_Mathc object; span=(0, 4), match=’1234’>
数本身不可改变,包括:
转换函数 | 说明 |
---|---|
int(x) | 返回x对应的整数 |
float(x) | 返回x对应的浮点数 |
complex(x) | 返回x对应的复数,实部为x,虚部为0 |
complex(x, y) | 返回x对应的复数,实部为x,虚部为y |
例:
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool(0)
False
>>> bool('')
False
>>> bool('0') #非空str为真
True
算术运算符通常由左向右结合,即2+3+4的计算顺序为(2+3)+4;
运算符 | 作用 | 说明 |
---|---|---|
+ | 加 | 字符串相加即“合并字符串”,‘a’+‘b’得到‘ab’ |
- | 减、负号 | 无 |
* | 乘、str重复 | ‘ab’*3得到‘ababab’ |
/ | 除 | 4/3和4/3.0结果相同,都是1.3333… |
% | 取模(余数) | 25.5%2.25得到0.75; -25.5%2.25得到1.5 |
/ | 整除 | 返回商的整数部分;4//3得1,4//3.0得1.0 |
* | 乘方 |
运算符 | 作用 | 说明 |
---|---|---|
== | 等于 | 可用于数、str、list等 |
!= | 不等于 | |
<> | 不等于,类似于!= | |
>、>= | 大于、大于等于 | |
<、<= | 小于、小于等于 |
赋值运算由右向左结合,即a=b=c的处理方式为a=(b=c)
>>> a,b,c=1,2,3
>>> a=b=c #赋值过程:b=c(3),然后a=b,最终a和b都是得到的c的值
>>> print(a,b,c) # a的值不是2,而是3
3 3 3
运算符 | 作用 | 说明 |
---|---|---|
= | 简单赋值 | |
+= | 加等 | c+=a 等价于c = c + a |
-= | 减等 | |
*= | 乘等 | |
/= | 除等 | |
%= | 模等 | |
**= | 幂等 | |
//= | 整除等 |
运算符 | 作用 | 说明 |
---|---|---|
& | 按位与 | 12&13返回12,即1100&1101=1100 |
| | 按位或 | 12|13返回13,即1100 |
^ | 按位异或 | 12^13返回1,即1100 |
~ | 按位取反 | ~x 类似于–x-1,即~60返回-61,见本表后面详细例子; |
<< | 左移 | 高位丢弃,低位补0。 6 << 4返回96,即110<<4得1100000,亦即6*(2**4) |
>> | 右移 | 低位丢弃,高位补0。 6>>2返回1,即110>>2得001 |
>>> bin(60) # 60的二进制数据为0011_1100
'0b00111100'
>>>
>>> ~60
-61
#
# ~60即~0011_1100得1100_0011,
# python把按位取反的值当补码解释(负数的补码为取反加1),所以返回-61.
#
# 注:-61的各码如下:
# 原码:1011_1101
# 反码:1100_0010(注,取反码时,符号位不取反)
# 补码:1100_0011,即60按位取反得的值,所以~60返回-61
运算符 | 作用 | 说明 |
---|---|---|
and | 布尔与 | x and y,会短路计算:x为False时,返回x,注意不是x的布尔值;x为True时,返回y,注意不是y的布尔值; |
or | 布尔或 | x or y,会短路计算:x为False时,返回y,注意不是y的布尔值;x为True时,返回x,注意不是x的布尔值; |
not | 布尔非 | x=true; not x; 返回False; |
>>> [] and {
1:‘a’} # 空数组为假,返回空数组,注意返回的不是x的布尔值False
[]
>>> [] or {
1:‘a’}# 空数组为假,返回y的值,注意不是y的布尔值;
{
1:‘a’}
>>> not {
1:‘a’} # not会返回布尔值
False
>>> not [] # not会返回布尔值
True
运算符 | 作用 | 说明 |
---|---|---|
in | 包含成员 | 在指定的序列中找到值,返回True |
not in | 不包含成员 |
if xxx:
a
elif yyy:
b
else:
c
while xxx:
a
else:
b
while对应的else语句在while循环正常结束的情况下会被执行.
如果while循环是通过break跳出的,则else不执行。
这种else可以在这种情况下:通过循环寻找某个东西,如果找到,则break退出,如果没找到,则执行完全部循环,然后执行else语句报告没有找到。
例1:
import random
import re
target = random.randint(0, 100)
cnt = 6
i = 0
while i<cnt:
guess = input('Round {i}, enter a num in [0,100]: '.format(i=i+1))
i += 1
guess = int(guess)
if guess>target:
print('Too large')
elif guess<target:
print('Too small')
elif guess==target:
print('Pass, target is {target}'.format(target=target))
break # 如果猜中数字,则跳出循环,不会执行else语句
else:
# 如果循环正常结束仍没猜中,执行else语句,报告Fail
print('Fail, target is {target}, You Have try for {cnt} times'.format(target=target, cnt=cnt))
输出:
Round 1,enter a num in [0,100]: 50
Too small
Round 2,enter a num in [0,100]: 75
Too small
Round 3,enter a num in [0,100]: 88
Too small
Round 4,enter a num in [0,100]: 96
Too small
Round 5,enter a num in [0,100]: 98
Too small
Round 6,enter a num in [0,100]: 99
Too small
Fail: target is 100, You Have Try for 6 times
#range返回一个列表,不包含结束值。
for i in range(1,5):
print(i)
else:
print('over')
for对应的else语句类似于while对应的else语句,在for循环正常结束的情况下会被执行,具体解释见while处的解释。
break: 终止循环
continue: 进入下一轮循环
def add(a,b=0):
c = a+b
global d, e
d = a*b
e = a+b
传入的值按照位置顺序依次赋给函数的形参。比如power(x, n)
可以在定义函数时,把后面的参数赋一个默认值,如果调用函数时不指定,则使用默认值;
def power(x, n=2):
pass
注意:
传入的参数个数是可变的:
def calc(*numbers): #定义函数时,在参数前加一个*号,则参数接收到的是一个tuple
sum=0
for n in numbers: #numbers是一个tuple,遍历它
sum += n*n
return sum
nums = [1,2,3]
calc(*nums) #调用函数时:在list/tuple前加一个*号,把list/tuple元素变成可变参数传入
可变参数允许传入0个或多个参数,在函数调用时组装为一个tuple;
关键字参数允许传入0个或多个含参数名的参数,这些关键字参数在函数内部组装为一个dict。
使用关键字参数可以扩展函数的功能;
>>> def person(name, age, **kw):
... print(‘name:’, name, ‘, age’, age, ‘, others:’ kw)
...
>>> person(‘Michael’, 30) #只传入关键字参数
name:Michael, age:30, others:{
}
>>>
>>> person(‘Adam’, 45, gender=’M’, job=’Engineer’) # 后面两个参数会组装成一个dict。
name:Adam, age:45, others:{
‘gender’:’M’, ‘job’:’Engineer’}
>>>
>>> extra = {
‘gender’:’M’, ‘job’:’Engineer’}
>>> person(‘Adam’, 45, **extra) #在dict前加**,把dict变成关键字参数传入;
name:Adam, age:45, others:{
‘gender’:’M’, ‘job’:’Engineer’}
关键字参数:可以传递任意名称的关键字作为参数;
命名关键字参数:可以限制关键字参数的名字,比如只接收city和job作为关键字参数;使用一个*号与前面的参数隔开;
>> def person(name, age, *, city, job):
.. print(name, age, city, job)
..
>> person(‘Jon’, 30, city=’Chengdu’, job=’Engineer’) # 通过参数名传参数,正常运行;
on 30 Chendu, Engineer
>>
>> extra = {
‘city’:’Shenzhen’, ‘job’:’Art’}
>> person(‘Bill’, 9, **extra) # 通过dict传入参数,正常运行;
ill 9 Shenzhen Art
>>>
>>> extra ={
‘job’:’Art’}
>>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:缺少city参数;
TypeError: person() missing 1 required keyword-only arg: ‘city’
>>>
>>> extra = {
‘length’:6, ‘job’:’Art’}
>>> person(‘Bill’, 9, **extra) # 通过dict传入参数,会报错:不能使用length这个参数;
TypeError: person() got a unexpected keyword arg: ‘length’
Python函数的5种参数可以任意组合使用, 参数定义的顺序必须为:
比如:
def f1(a, b, c=0, *args, **kw):
pass
def f2(a, b, c=0, *, d, **kw):
pass
任意函数,都可以通过类似func(*args, **kw)的形式(一个元组和一个字典)调用它,无论它的参数是如何定义的。
3.1.10. 与**的作用
一般与**只用在给函数传参数时,在函数参数外使用作用不大(还可能报错)
比如:
foo(1, 2, 3) 相当于foo(*[1, 2, 3])
bar(a=1, b=2, c=3) 相当于bar(**{‘a’:1, ‘b’:2, ‘c’:3})
*L0 可以认为是把列表(元组)L0拆散后的结果, 。
*的作用:将列表拆散为元素。
L0 = [0, 1, 2, 3] # L0是list,是一个整体,把L0传递给函数,函数得到的是1个参数。
*L0 # L0是4个元素,把L0传递给函数,函数得到的是4个参数。
**的作用,将dict拆散为一系列键值对。
D0 = {‘a’:0, ‘b’:1} # D0是dict,直接传递给函数, 函数得到的是1个参数。
D0 # 是两个键值对, 把D0传递给函数,函数得到的是2个参数。
def move(x0, y0, s=0, angle=0):
x1=x0+s*math.cos(angle)
y1=y0+s*math.sin(angle)
return x1,y1
x,y = move(100,100, 50, math.pi*(1/3))
>>> def max(x,y):
... '''''Return the max of two number.
... The two number must be integer.'''
... if a>b:
... return a
... else:
... return b
...
>>> print max(1,2)
>>> print max.__doc__
函数名其实是指向一个函数的引用,所以可以把函数名赋给一个变量,然后可以后这个变量调用函数:
>>> a = abs # 变量a指向abs函数
>>> print(a(-1))
1
>>> print(a)
<built-in function abs>
Python对函数式编程提供部分支持;
由于Python允许使用变量,所以Python不是纯函数式编程语言;
>>> x = abs(-10) # 调用函数,并获得函数调用结果;
>>> f = abs # 把函数赋值给变量
>>> print(f) #
>>> f(-10) # 变量f指向abs,f(-10)与abs(-10)完全相同
函数名其实就是指向函数的变量。
比如abs()这个函数,abs就是一个变量,指向计算绝对值的函数;
>>> import builtins
>>> builtins.abs
<built-in function abs>
>>> builtins.abs = 10 # 可以把abs函数的指向改变
一个函数A可以接收另一个函数B作为参数,这种函数A就称之为高阶函数;
>>> def func_add(f, x, y):
... return f(x)+f(y)
...
>>> abs_add(abs, 5, -6)
11
map()、reduce()、都是高阶函数
高阶函数除了可以接受函数做为参数外,还可以把函数做为结果值返回;
>>> foo = [2,18,9,22]
>>> list(filter(lambda x:x%3==0, foo)) #列表中可以被3整除的数;
[18, 9]
>>> list(map(lambda x:x*2+10, foo)) #将原列表元素做*2+10;
[14, 46, 28, 54]
>>> list(map(lambda x:[x,x*2], foo)) #将列表元素映射为list;
[[2, 4], [18, 36], [9, 18], [22, 44]]
>>> functools.reduce(lambda x,y:x+y, foo) # 返回数值,将原列表相加;
51
上述filter可以改写为:[x for x in foo if x%x==0]
上述map可以改写为:[x*2+10 for x in foo]
作用:增强函数的功能,但又不修改函数定义本身;
例1:
# 6. 定义一个装饰器,它可以打印当前调用函数的名称,它接受一个函数作为参数,并返回一个函数
>>> def log(func):
... def wrapper(*args, **kwargs):
... print(‘call func: {
name}: ’.format(name=func.__name__))
... return func(*args, **kwargs)
... return wrapper
#把装饰器(log)作用到其它函数(now)上,相当于now=log(now):
#调用now(),在执行now本身之前,会先打印一行log
>>> @log
... def now():
... print(datetime.datetime.now())
...
>>> import datetime #可以在调用now之前才import它需要用到的模块
>>> now()
call func: now
2018-07-27 11:29:18.239895
例2,定义一个装饰器,打印函数调用的次数和函数执行时间
import time
def timer(func):
lst = [0, 0] # 使用一个list保存要记录的内容,在每次调用时,lst指向同一地址;
def wrapper(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs) #调用函数,并保留返回值
end = time.time()
delta = end –start
lst[0] += 1 # lst[0]记录函数执行的次数
lst[1] += delta # lst[1]记录函数执行的时间
print(f‘cnt: {
lst[0]}, time: {
lst[1]}, {
delta}’)
return r # 返回函数的结果,这里有个局限性,只能处理返回单个值的函数;
return wrapper # 返回包了壳的函数
测试:
>>> @timer
>>> def my_sum(n): #定义一个函数
... r = 0
... for i in range(n):
... r += i
... return r
...
>>> for i in range(10):
... r = my_sum(10000000)
...
cnt 1, time 0.937***, 0.937***
cnt 2, time 1.852***, 0.914***
cnt 3, time 2.747***, 0.895***
cnt 4, time 3.681***, 0.934***
cnt 5, time 4.601***, 0.919***
cnt 6, time 5.563***, 0.962***
cnt 7, time 6.494***, 0.930***
cnt 8, time 7.403***, 0.909***
cnt 9, time 8.374***, 0.970***
cnt 10, time 9.280***, 0.906***
Python的functools模块提供了很多功能,其中一个是偏函数(Partial Function);
functools.partial功能:帮忙我们创建一个偏函数,但不需要我们自己定义偏函数;
例如:将2进制字符串转为10进制整数,可以定义一个函数int2
def int2(x, base=2):
return int(x, base=base)
这样默认处理2进制字符串.
但我们不需要自己定义函数int2,可以使用偏函数达到目的:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2(‘00010000’)
16
functools.partial的作用:把函数的某些参数给固定住(即设置默认值),返回一个新的函数;
但是调用int2时,仍然可以给它指定base参数为其它值:
>>> int2(‘00010000’,base=10)
10000
例1:
>>> int2 = functools.partial(int, base=2)
>>> int2(‘00010000’)
>>>
>>> #相当于:
>>> kwargs = {
‘base’:2} # 将’base’:2作为kwargs的一部分
>>> int(‘00010000’, **kwargs)
例2:
>>> max_with_10 = functools.partial(max, 10)
>>> max_with_10(5, 6, 7)
>>>
>>> #相当于:
>>> args=(10, 5, 6, 7) # 将10作为*args的一部分自动加到左边
>>> max(*args)
假设模块名称也冲突了,可以通过包来组织模块,避免冲突。
方法是选择一个顶层包名,按目录mycomp存放;这样模块名就成了mycomp.abc
包目录下必须有一个__init__.py文件,否则Python把目录当普通目录,而不是一个包。
作用:比输入模块要快。
from sys import argv
from sys import *
可以把sys模块的使用的名字输入。
建议使用import sys,程序易读且避免名称冲突。
1. if __name__ == '__main__':
2. print('this program is running by itself')
3. eles:
4. print('this program is imported from another module')
1. #file: mymodule.py
2. version = ‘1.0’
3. def sayhi():
4. print ‘hello world’
5.
6. #file: test1.py
7. >>> import mymodule #mymodule.py需要在当前目录或sys.path指示的目录中。
8. >>> mymodule.sayhi()#一定要带小括号
9. hello world
10. >>> print mymodule.version
11. '1.0'
12.
13. #file: test2.py
14. >>> from mymodule import sayhi, version
15. >>> sayhi()
16. hello world
17. >>> print(version)
18. '1.0'
作用:列出模块定义的标识符(三种:函数、变量、类)
参数:提供模块名时,返回该模块的标识符,不提供模块名时,返回当前模块的标识符。
1. >>> import mymodule
2. >>> dir()
3. [‘__builtins__’,’ __doc__’,’ __file__’,’ __name__’,’ __mymodule__’]
4. >>> dir(mymodule) #注意mymodule不要加引号,否则不会当作module名处理
5. [‘__builtins__’,’ __doc__’,’ __file__’,’ __name__’,’ __sayhi__’ ,’ __version__’]
6. >>>
7. >>> del mymodule #删除一个标识符,后面将不能再用它
8. >>> dir()
9. [‘__builtins__’,’ __doc__’,’ __file__’,’ __name__’]
注: 可以通过help(list)获取list的完整知识;
常用方法
函数名 | 用法 | 说明 |
---|---|---|
append | lst.append(item) | 列表末尾添加一个元素 |
extend | lst.extend(lst_new) | 列表末尾添加lst_new的元素 |
insert | lst.insert(i, item) | 在位置i添加元素item |
pop | lst.pop(i) | 删除并返回位置i的元素, i默认为最后一项,也可以使用del lst[i] |
sort | lst.sort() | 对lst进行排序, 会更改lst, 不返回lst |
reverse | lst.reverse() | lst逆序, 会更改lst, 不返回lst |
index | lst.index(item) | 返回lst中第一次出现item的idx |
count | lst.count(item) | 返回lst中value==item的个数 |
remove | lst.remove(item) | 删除lst中第一个value==item的元素, 不返回值 |
len | len(lst) | 返回元素个数/lst的长度 |
具体使用示例
列表清空/列表赋值
1. >>> abc = [] #清空列表(把空列表赋值给变量):
2. >>> shoplist = [‘apple’, ‘mango’, ‘carrot’, ‘banana’] # 赋值多个元素
列表遍历
1. >>> for ele in shoplist:
2. ... print(ele)
列表长度(元素个数)
1. >>> len(shoplist)
2. 4
列表追加列表/列表合并/列表排序
1. >>> L1.extend(L2) # 在列表L1结尾追加列表L2中的元素;
2. >>> L3 = L1 + L2 # 列表合并;
3. >>> L1.sort() # 列表排序,注意,列表本身会改变,而不是返回一个被改变的列表,因为列表是可变的。该语句返回None;sort()详细使用见第15.9节;
获取指定idx对应的元素/获取指定元素对应的idx
1. >>> shoplist[0] #根据下标获取列表元素, 返回元素:
2. ‘apple’
3. >>>
4. >>> #取元素对应的index:
5. >>> shoplist.index(‘apple’) #根据列表元素获取下标, 返回下标, 如果元素不存在, 则raise ValueError
6. 0
列表追加元素
1. >>> shoplist.append(‘rice’) # 给列表追加元素,返回None
2. >>> shoplist
3. [‘apple’, ‘mango’, ‘carrot’, ‘banana’, ‘rice’]
指定idx添加元素
1. >>> shoplist.insert(2, ‘cup’) # 指定index添加元素,返回None
2. >>> shoplist # cup成为idx=2的元素,原本idx>=2的元素的idx都增加1
3. [‘apple’, ‘mango’, ‘cup’, ‘carrot’, ‘banana’, ‘rice’]
指定idx删除元素
1. >>> ele = shoplist.pop(2) # 指定index删除元素,返回元素, 默认删除最后一项;
2. >>> ele
3. ‘cup’
4. >>> shoplist
5. [‘apple’, ‘mango’, ‘carrot’, ‘banana’, ‘rice’]
6. >>>
7. >>> ele = shoplist.pop() #不指定idx,则删除末尾元素
8. >>> ele
9. ‘rice’
10. >>> shoplist
11. [‘apple’, ‘mango’, ‘carrot’, ‘banana’]
12.
指定value删除元素
1. >>> lst = list('hello world')
2. >>> lst
3. ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']
4. >>> lst.remove('o') # 删除第一个出现的'o'
5. >>> lst
6. ['h', 'e', 'l', 'l', ' ', 'w', 'o', 'r', 'l', 'd']
计算某元素出现的次数
1. >>> shoplist.count(‘carrot’) # 返回int,如果元素不存在,则返回0;
2. 1
遍历list时对循环变量赋值不会改变原本的list.
1. >>> a = [0, 1, 2, 3, 4]
2. >>> for e in a:
3. ... e = 5 # 直接给元素变量赋值不会改变list本身(与Perl有区别)
4. ...
5. >>> print(a) # [0, 1, 2, 3, 4],遍历list元素并对元素赋值不会改变list。
6.
列表生成式用来简洁在创建一个列表或从一个列表导出另一个列表;
1. >>> list(range(1,11)) # 生成从1到10组成的列表
2. >>> [x**2 for x in range(1,11)] # 生成从1到10的平方组成的列表
3. >>> [x**2 for x in range(1,11) if x%2==0] # 生成从1到10的中的偶数的平方组成的列表;
4. >>> [m+n for m in ‘ABC’ for n in ‘XYZ’]# 两层循环,生成ABC与XYZ字符的排列
5. >>> [d for d in os.listdir(‘.’)] # 列表当前目录下的所有文件和文件夹
6. >>> [k+‘=’+v for k,v in d.items()] # for循环使用两个变量,把k和v用等号连接
7. >>> [s.lower() for s in L] # 把list中的字符串改成小写;
列表容量有限,一边循环一边计算生成列表,这种方式叫:generator;
1. >>> L = [x**2 for x in range(10)] # 列表生成式;打印L可以打印出所有元素;
2. >>> g = (x**2 for x in range(10)) # generator;与列表生成式的区别是小括号;
3. >>> g #打印g会得到,这个地方保存的是算法,而不是实际的列表;
4. <generator object <genexpr> at 0x1022ef630>
多次使用next(g)可以依次取出值,但比较傻;
generater是可迭代对象,所以可以使用循环:
>>> for n in g:
... print(n, end=’ ’)
...
0 1 4 9 16 25 36 49 64 81
打印过一次后,再循环g,就没有内容了;
如果一个函数定义中包含yield关键字,则函数就不再是一个普通函数,而是一个generator.
斐波拉契数列:
1. >>> def fib(max):
2. ... a, b = 0, 1
3. ... for i in range(max):
4. ... yield b # 每次调用next或每次循环,执行到这就会暂停,返回这个值;
5. ... a, b = b, a+b
6. ... return ‘Done’ # generator的return值需要通过捕获StopIteration拿到;
7. ...
8. >>> f = fib(6)
9. >>> f
10. <generator object fib at ox2ac****>
11. >>> for i in f:
12. ... print(i, end=‘ ’)
13. ...
14. 1 1 2 3 5 8
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator;
生成器都是Iterator对象,但list、dict、str虽然Iterable,但不是Iterator;
1. import collections
2.
3. isinstance(100, collections.Iterable) # False,整数不是可迭代的
4.
5. isinstance([], collections.Iterable) # True , 列表是可迭代的;
6. isinstance([], collections.Iterator) # False,列表不是迭代器;
7.
8. _gen = (x for x in range(10))
9. isinstance(_gen, collections.Iterable) # True,generator是可迭代的;
10. isinstance(_gen, collections.Iterator) # True,generator是迭代器;
使用iter()函数,可以把list/dict/str变成Iterator;
元组不可变,主要是为了代码安全;
如果元组的元素是列表,则元组不可变决定了这个元素要一直指向这个列表,但列表的内容却是可以改变的;
1. #元组赋值
2. zoo = (‘wolf’, ‘elephant’, ‘penguin’)
3.
4. #空元组的赋值
5. empty = ()
6. #只含一个元素的元组的赋值:
7. single_item = (2,) #注意必须要有个逗号,以区别于表达式中一个带括号的对象;
8.
9. # 获取元组长度:len(zoo)
10. # 元组的元素可以是另一个元组或列表(列表的元素也可以是另一个元组或列表):
11. new_zoo = (‘monkey’,’dolphin’, zoo)
12. # 注意,此处与Perl不同,Perl数组中直接放一个数组会被打散;
13.
14. # 根据下标获取元组的元素:
15. zoo[0],new_zoo[2],new_zoo[2][2];
16.
17. # 元组在print语句的应用:
18. print ‘%s is %d years old’%(name,age) # %后面加元组表示定制元组输出;
19.
20. # 元组在print语句的应用:
21. print ‘%s is playing with that python’%name # 当只定制一个元素时,可以不使用圆括号。
使用help(dict)获取完整方法列表;
1. >>> #字典赋值:
2. >>> addr_book = {
‘Swa’:’swa@123.com’, ’Larry’:’larry@124.com’ ,’Mat’:’mat@125.com’,’Spa’:’spa@126.com’}
3.
4. >>> #获取key对应的value
5. >>> addr_book[‘Swa’] # 返回值为key对应的value
6. ‘swa@123.com’
7. >>> addr_book.get(‘xxx’, -1) #使用get(),-1是指定(在key不存在时)的返回值
8. -1
9.
10. >>> #添加一对key:value:
11. >>> addr_book[‘Gui’] = ‘gui@127.com’
12.
13. >>> #删除一对key:value
14. >>> addr_book.pop(‘Spa’) # 返回值为value
15. ‘spa@126.com’
16.
17. >>> #获取字典的k:v对数
18. >>> len(addr_book)
19. 4
20.
21. >>> #添加多对key:value:
22. >>> addr_book.update({
k0:v0, k1:v1})
23. >>>
24. >>> #遍历字典:
25. >>> for name, addr in addr_book.items(): #迭代dict中的k-v对
26. ... print ‘%s : %s’%(name, addr)
27. ...
28. >>> for name in addr_book: # 迭代dict中的key, 也可以用addr_book.keys()
29. ... addr = addr_bood[name]
30. ... print ‘%s : %s’%(name, addr)
31. ...
32. >>> for addr in addr_book.values(): # 迭代 dict中的value
33. ... print ‘%s’%( addr)
34. ...
35.
36. >>> #判断key是否存在:
37. >>> ‘Swa’ in addr_book
38. True
常用方法
函数名 | 用法 | 说明 |
---|---|---|
union | A.union(B) | 返回A和B的并集, A/B本身不变 |
intersection | A.intersection(B) | 返回A和B的交集, A/B本身不变 |
difference | A.difference(B) | 返回在A中出现, 在B中没出现的item组成的 set, A-(A&B) |
issubset | A.issubset(B) | A是否是B的子集, 返回True/False |
add | A.add(item) | 给A集合添加一个item |
remove | A.remove(item) | 给A集合删除item这个元素 |
1. >>> s1 = {
1, 2, 3} # 一个set
2. >>> s2 = {
2, 3, 4} # 又一个set
3. >>>
4. >>> s1 & s2 # {2, 3}, 交集
5. >>> s1 | s2 # {1, 2, 3, 4}, 并集
6. >>> s1 - s2 # {1}, 减运算
7. >>> s1 + s2 # 不支持+运算, 可以使用|运算
8. Traceback (most recent call last):
9. File "", line 1, in <module>
10. TypeError: unsupported operand type(s) for +: 'set' and 'set'
11. >>>
12. >>> {
1,2,3} < {
1, 2, 3, 4} # True, 支持<, <=, >, >=运算
列表、元组、字符串都是序列,序列有两个主要操作:索引、切片。
### 6.1.1. 索引(抓取一个元素)操作:
>>> shop_list[n]# 取第n个元素
>>> shop_list[-1] # 取最后一个元素
>>> shop_list[-2] # 取倒数第二个元素
语法:a=[0, 1, 2, 3, 4] ; a[i:j:k]
参数:
切片参数 | 说明 | 默认值(k>0) | 默认值(k<0) |
---|---|---|---|
i | 起始位置 | 0 | -1 |
j | 结束位置 | len(a) | -1-len(a) |
k | 步长 | 1 | 1 |
正负索引值的关系:idxr = idx-len(a)
正/负 | 序号 | 索引值:idxr=idx-len(a) | ||||||
---|---|---|---|---|---|---|---|---|
正值 | idx | 0 | 1 | 2 | ... | len(a)-3 | len(a)-2 | len(a)-1 |
负值 | idxr | 0-len(a) | 1-len(a) | 2-len(a) | ... | -3 | -2 | -1 |
以a=[0, 1, 2, 3, 4]为例说明:
常用取元素(以a=[0, 1, 2, 3, 4]为例) 操作:
1. >>> a[1:4] # [1, 2, 3],第1到第3个元素,不包含第4个元素;
2. >>> a[2:] # [2, 3, 4],第2到最后一个元素;
3. >>> a[2:len(a)] # [2, 3, 4],同a[2:];
4. >>> a[2:-1] # [2, 3],第2到倒数第2个元素,注意不包含a[-1];
5. >>> a[:3:2] # [0, 2],第0~2个元素,每两个元素取一个。
6. >>> a[:] # [0, 1, 2, 3, 4],所有元素;
7. >>> a[::2] # [0, 2, 4],所有元素,每两个元素取一个;
8. >>> a[::-1] # [4, 3, 2, 1, 0],逆序list;
9. >>> for e in a[::-1]: # 通过将k置为-1,逆序操作list;
10. ... print(e)
常用替换元素(a=[0, 1, 2, 3, 4]为例)操作:
1. >>> a[0:2] = [‘a’, ‘b’, ‘c’]
2. >>> a
3. [‘a’, ‘b’, ‘c’, 2, 3, 4]
说明:将第0~1个元素替换为[‘a’, ‘b’, ‘c’], 注意:k=1时,切片元素数目和替换元素数目可以不相等。
1. >>> a[0:2] = []
2. >>> a
3. [2, 3, 4]
说明:删除第0~1个元素;也可以写成a[0:2] = ‘’或a[0:2]={}, 但不能写成a[0:2]=None,会报告TypeError;
1. >>> a[0::2] = [‘a’, ‘b’, ‘c’]
2. >>> a
3. [‘a’, 1, ‘b’, 3, ‘c’]
说明:将第0,2,4个元素替换为[‘a’, ‘b’, ‘c’] 注意,k!=1时,如果切片元素数和替换元素数不相等会报错。
1. >>> a[0::2] = [] # k!=1,切片元素数与替换元素数不相等,报错;
2. ValueError: attempt to assign sequence of size 0 to extended slice of size 3
3. >>>
4. >>> a[-4:-6:-1] = [‘a’, ‘b’, ‘c’] # 报错,k==-1时也要求替换数目相等
5. ValueError: attempt to assign sequence of size 3 to extended slice of size 2
说明:即使k=-1,切片元素数和替换元素数不相等也会报错;
1. >>> a[-3:-6:-1] = ['a', 'b', 'c']
2. >>> a
3. ['c', 'b', 'a', 3, 4,]
4. #说明:将第2~0(-3, -4, -5)个元素替换为['a', 'b', 'c'],
5. # 反映到a中,'a''b''c'出现的次序会反向;
5.反向替换,k!=1
1. >>> a[::-2] = ['a', 'b', 'c']
2. ['c', 1, 'b', 3, 'a']
3. # 说明:第0, 2, 4 (-1,-3,-5)个元素替换为['a', 'b', 'c']
1. >>> name = ‘swaroop’
2. >>> name[1:3] # ‘wa’
3. >>> name[2] # ‘a’
4. >>> name[1:-1] # ‘waroo’, 不包括最后一个元素‘p’
5. >>> name[:] # ‘swaroop’
将一个对象赋值给一个变量时,变量得到的是对象的引用,而不是对象本身。
1. >>> L0 = [‘a’,’b’,’c’,’d’,’e’]
2. >>> L1 = L0# L1和L0是同一个东西的两个不同的名字
3. >>> L0.pop(0)# L0和L1开头的元素被删除
4. ‘a’
5. >>> L1.pop(-1)# L0和L1结尾的元素被删除
6. ‘e’
7. >>> L0
8. [’b’,’c’,’d’]
9. >>> L1
10. [’b’,’c’,’d’]
11. >>> L1 is L0 # L1和L0是同一个东西
12. True
如果想将list拷贝一份赋给另一个list,需要使用切片操作:
1. >>> L2 = L0[:]# 使用切片,这时L2和L0是独立的(list类的)对象。
2. >>> L2 is L0 # L2和L0不是同一个东西
3. False
4. >>> L2 == L0 # L2和L0的内容相同
5. True
字符串都是str类的对象。
help(str)可以查看str类的完整信息。
作用:返回sub在string里出现的次数。如果指定了start或end,则返回指定范围内出现的次数。
参数:
实例:
1. >>> s = ‘1abc2abc3abc’
2. >>> s.count(‘abc’) # ‘abc’共出现了3次;
3. 3
4. >>> s.count(‘abc’, 0, 3) # end=3不包括idx=3的字符,所以返回0;
5. 0
6. >>> s.count(‘abc’, start=0) # 不能使用关键字参数;
7. TypeError: count() takes no keyword arguments
作用:检测字符串是否包含子串str;
返回值:
参数:
实例:
1. >>> s0 = ‘This isa string’
2. >>> s1 = ‘is’
3. >>> s0.find (s1) # 2,返回s1在s0中的起始idx。
4. 2
5. >>> s0.find (s1.upper()) # s0中不包含’IS’,返回-1
6. -1
7. >>> s0.find(s1, 2) # 从idx==2开始找,包括idx==2
名词 | 解释 |
---|---|
类 | 类创建一个新类型;是一个抽象的模板; |
对象/实例 | 类的实例;每个对象拥有相同的方法,但数据可能不同; |
域 | 属于一个类或对象的变量,用于存储数据;有两种类型:实例变量、类变量; |
方法 | 属于一个类的函数; |
属性 | 域和方法合称为属性; |
实例变量 | 属于每个实例(类的对象)的域; |
类变量 | 属于类本身的域; |
实例的变量如果以双下划线开头,表示是一个私有变量;外部不能访问,只能通过添加方法get_name、set_name等来访问或修改;
>>> bart = Student('Bart Simpson', 98)
>>> bart.__name # 报错,object has no attribute '__name'
>>> bart.__name = 'New Name'
#强制设置private变量,可以打印它,
#但它只是外部代码给bart新增了一个__name变量
#(内部的__name已经被python解释器改成了_Student__name),
#使用bart.get_name(),仍然返回原本的值。
以双下划线开头且以双下划线结尾的,是特殊变量,可以外部访问,不是private;
以单下划线开头的实例变量,外部可以访问,但要视为private,不要随意访问;
子类(subclass):继承得到的新的class;
基类、父类、超类(base class、super class):被继承的class;
继承的好处:1)子类获得了父类的全部功能;2)多态;
如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,反过来不行;可以使用isinstance判断类型,如:isinstance(c, Animal)
当我们需要对函数传入Dog、Cat、Tortoise时,函数参数只需要接收Animal类型就可以了。
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数;
type()函数用来获取对象类型
type()返回的是class类型,所以相同类型的比较会返回True, 直接与类型比较也返回True:
使用types包
常用的类型有:
types.FunctionType,
types.BuiltinFunctionType,
types.LambdaType,
types.GeneratorType
>>> import types
>>> def fn()
... pass
...
>>> type(fn) == types.FunctionType # True
>>> type(abs) == types.BuiltinFunctionType # True
>>> type(lambda x:x) == types.LambdaType # True
>>> type((x for x in range(10))) == types.GeneratorType # True
语法:isinstance(obj, class_or_tuple, /)
参数:参数2可以是一个class,也可以是class组成的元组,元组中的class是“或”的关系;
对于有继承关系的class,可以使用isinstance()判断class类型;
>>> class A(object):
... pass
...
>>> class B(A):
... pass
...
>>> b0 = B()
>>> isinstance(b0, B) # True, b0是B的实例
>>> isinstance(b0, A) # True, b0也是A的实例
参数2可以是一个元组
>>> isinstance((1, 2, 3), list ) # False, 不是list
>>> isinstance((1, 2, 3), tuple ) # True , 是tuple
>>> isinstance((1, 2, 3), (list, tuple)) # True , 是list或tuple
还可以判断对象是不是可迭代的:
>>> import collections
>>> isinstance('abc' , collections.Iterable) # True, str可迭代
>>> isinstance([1,2,3] , collections.Iterable) # True, list可迭代
>>> isinstance({
1:2, 3:4}, collections.Iterable) # True, dict可迭代
>>> isinstance(123 , collections.Iterable) # False,int不可迭代
dir()函数用于获取一个对象的所有属性和方法;比如dir(‘ABC’)返回字符串的属性和方法;
例:先定义一个class
>>> class Dog():
... def __init__(self, name):
... self.name = name
... def getName(self):
... return self.name
使用dir获取属性和方法(这种方式不能区分哪个是属性哪个是方法):
>>> d0 = Dog('XiaoBai')
>>> attrs = [ele for ele in dir(d0) if not ele.startswith('__')]
>>> attrs
['getName', 'name']
使用hasattr判断某对象是否包含某属性/方法, 然后使用callable判断它是属性还是方法
例:
>>> hasattr(d0, 'name') # d0有'name'属性
True
>>> hasattr(d0, 'getName') # d0有'getName'方法
True
>>> hasattr(d0, 'age')
False
>>>
>>> callable(d0.name)
False
>>> callable(d0.getName) # getName可调用, 它是个方法
True
使用getattr和callable联合判断:
>>> name = getattr(d0, 'name', None)
>>> if callable(name):
... print('name is callable')
... else:
... print('name is not callable')
...
name is not callable
>>>
>>> getName = getattr(d0, 'getName', None)
>>> if callable(getName):
... print('getName is callable')
... print(getName()) # 调用方法
... else:
... print('getName is not callable')
...
getName is callable
Xiaobai
>>>
>>> getName
<bound method Dog.getName of <__main__.Dog object at 0x2b***>>
>>> getName()
'Xiaobai'
使用setattr(), 如果属性存在, 则修改该属性的值, 如果属性不存在, 则添加该属性
>>> setattr(d0, 'name', 'Dabai') # 设置'name'属性
>>> d0.name
'Dabai'
>>> setattr(d0, 'color', 'White') # 设置不存在的属性,则添加该属性;
>>> d0.color
'white'
类的方法是函数,与普通函数的区别是:类的方法必须要有一个额外的参数,这个参数是方法的第一个参数,但是,在调用这个方法的时候不用为这个参数赋值。这个参数指对象本身,一般命名为self。例如:
MyObject = MyClass()
MyObject.method(arg1, arg2) # python会自动转为MyClass.method(MyObject, arg1, arg2),注意,是用MyClass调用的method,而不是MyObject。
所以,即使一个方法没有参数,还是得给这个方法定义一个self参数;
__init__方法不需要专门调用,只需要在创建类的实例时把参数写在圆括号内就可以传递给__init__方法。
数据封装、继承、多态只是面向对象程序设计的基础概念,还有高级特性,比如:
多重继承、定制类、元类等;
指定一个元组,当前类只能绑定该元组的元素作为属性。
>>> class Student(object):
... __slots__ = (‘name’, ‘age’) # 使用元组定义允许绑定的属性名称
...
>>> s = Student()
>>> s.score = 99 # 给实例绑定score属性会报错;
AttributeError: ‘Student’ object has no attribute ‘score’
__slots__定义的属性仅对当前类实例起作用,对继承的子类不起作用;
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 999 # g是GraduateStudent(Student子类)的实例,所以Student类的__slots__对g不起作用,可以组g绑定score属性;
@property装饰器,负责把一个方法变成属性调用;
传统做法:定义两个方法get_score()和set_score(),分别用来访问值和设置值;
class Student0(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be a integer!')
elif value<0 or value>100:
raise ValueError('score must between 0 and 100!')
self._score = value
>>> s0 = Student0()
>>> s0.set_score(60)
>>> s0.get_score()
60
@property做法:
class Student1(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be a integer!')
elif value<0 or value>100:
raise ValueError('score must between 0 and 100!')
self._score = value
>>> s1 = Student1()
>>> s1.score=60 # 实际操作为s1.set_score(60)
>>> s1.score# 实际操作为s1.get_score()
60
多重继承时,super()的执行顺序
例1:
class继承关系
class C00():
def run(self):
print('run C00')
class C10(C00):
def run(self):
super(C10, self).run()
print('run C10')
class C11(C00):
def run(self):
super(C11, self).run()
print('run C11')
class C20(C10, C11):
def run(self):
super(C20, self).run()
print('run C20')
执行:
>>> C20.__mro__
(<class ‘__main__.C20’>, <class ‘__main__.C10’>, <class ‘__main__.C11’>, <class ‘__main__.C00’>, <class ‘object’>)
>>> c = C20()
>>> c.run()
run C00
run C11
run C10
run C20
解释:
C20.__mro__是一个元组,采用广度优先原则(同一层级的优先,不同于深度优先),每个super函数调用的都是C20.__mro__中下一个元素对应类的函数;
>>> C20.__mro__
(
<class '__main__.C20'>,
<class '__main__.C10'>,
<class '__main__.C11'>,
<class '__main__.C00'>,
<class 'object'>,
)
/----------------\ /----------------\ /----------------\ /----------------\
| | |C20.__mro__[1]: | |C20.__mro__[2]: | |C20.__mro__[3]: |
->| | ->|cls __main__.C10| ->|cls __main__.C11| ->|cls __main__.C00|
/ |C20.run() | / |C10.run() | / |C11.run() | / |C00.run() |
/ \----------------/ / \----------------/ / \----------------/ / \----------------/
/ | / | / | / |
/----------------\ /----------------\ /----------------\ /----------------\ |
| c = C20() | |super(C20, self)| |super(C10, self)| |super(C11, self)| |
| c.run() | | .run() | | .run() | | .run() | |
| (1) | | (2) | | (3) | | (4) | |
\----------------/ \----------------/ \----------------/ \----------------/ | #output
| | | (5) print('run C11')--"run C00"
| | (6) print('run C11')---------------------------"run C11"
| (7) print('run C10')----------------------------------------------------"run C10"
(8) print('run C20')---------------------------------------------------------------------------- "run C20"
str:改变 print(类实例)时显示的内容。
普通class
>>> class CTest0():
... pass
...
>>> t0 = CTest0()
>>> t0
<__main__.CTest0 object at 0x2b***>
>>> print(t0)
<__main__.CTest0 object at 0x2b***>
重构__str__()
>>> class CTest1():
... def __str__(self):
... return ‘Class {
} obj’.format(self.__class__.__name__)
...
>>> t1 = CTest1()
>>> t1 # 直接输出对象,与默认情况相同
<__main__.CTest1 object at 0x2b***>
>>> print(t1) # 打印类实例,输出__str__()方法的返回值
<Class CTest1 obj>
repr:改变直接输出对象和 print(类实例) 时显示的内容
重构__repr__()
>>> class CTest2():
... def __repr__(self):
... return ‘Class {
} obj’.format(self.__class__.__name__)
...
>>> t2 = CTest2()
>>> t2 # 直接输出对象,输出__repr__()方法的返回值
<Class Ctest2 obj>
>>> print(t2) # 打印类实例,输出__repr__()方法的返回值
<Class CTest2 obj>
iter()方法:返回一个迭代对象;
next()方法:迭代__iter__()返回的对象时,会调用__next__()方法拿到循环的下一个值,直到遇到StopIteration错误;
class Fib():
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a+self.b
if self.a > 100:
raise StopIteration()
return self.a
>>> #对class 实例进行循环
>>> for i in Fib():
... print(i, end=‘ ’)
...
1 1 2 3 5 8 13 21 34 55 89
使用__iter__()方法虽然可以对类实例做循环,但不能用下标取元素,
使用__getitem__()方法可以实现“用下标取元素“;
class Fib():
def __getitem__(self, n):
a, b = 1, 1 # 如果n==0,则不进入for循环,直接返回1,保证[0]==1
for i in range(n):
a, b = b, a+b
return a
>>> #对类实例进行取下标
>>> Fib()[8]
34
>>> f = Fib()
>>> f[8]
34
使用__getitem__()可实现“按下标取元素“,但不能处理切片、不能“按下标赋值”、不能删除元素,这些都可以通过添加相应的方法来完成。
拦截对[‘’]方式的属性调用
class A:
def __init__(self, cfg={
}):
self.cfg = cfg
def __setitem__(self, k, v):
self.cfg[k] = v
def __getitem__(self, k):
return self.cfg[k]
当调用不存在的属性时,正常情况下会报告AttributeError: ‘XX’ object has no attribute ‘YY’;
可以通过__getattr__()方法动态返回一个属性,这时如果调用不存在的属性时,Python就会调用__getattr__(self, ‘attr’)来获取属性.
class Student():
def __init__(self):
self.name = ‘Jim’
def __getattr__(self, attr):
if attr == ‘score’:
return 99
raise AttributeError(‘\’Student\’ object has no attribute xx’)
>>> # 尝试调用属性score:
>>> s = Student()
>>> s.name
‘Jim’
>>> s.score # 本没有score这个属性,但通过__getattr__()方法获取了返回值
99
拦截属性取值语句, 即 a = obj.xx
拦截属性的赋值语句, 即 obj.xx = yy
class F(object):
def setattr(self, key, value):
self.dict[key] = value
使用__call__()方法可以使类实例变得“可调用”,类实例就象一个函数一样;
class Student():
def __init__(self, name):
self.name = name
def __call__(self):
print(f’My name is {
self.name}’)
>>> #尝试调用类实例
>>> s = Student(‘Jim’)
>>> s() # 此句会调用类的__call__()方法
My name is Jim
new()与__init__()的区别:
class Person(object):
def __new__(cls, *args, **kwargs):
print(f'__new__() called. {args} {kwargs}')
return super(Person, cls).__new__(cls) #NOTE, there is no args
def __init__(self, name, age):
print('__init__() called.')
self.name = name
self.age = age
def __str__(self):
return f''
p = Person('n0', age=24)
print(p)
执行结果如下:
__new__() called. ('n0',) {
'age': 24} # new方法先执行
__init__() called. # init方法后执行
<Person: n0(24)>
对于p = Person(name, age),
new()方法的使用场景:
class PosInt(int):
def __init__(self, i):
super().__init__() #NOTE: here takes no parameters.
i0 = PosInt(-3)
print(i0) # -3
class PosInt(int):
def __new__(cls, i):
return super().__new__(cls, abs(i))
i0 = PosInt(-3)
print(i0) # 3
重新定义类的==行为.
class CTest():
def __init__(self, i_value):
self.i_value = i_value
def __eq__(self, other):
#当两个实例的value相差<4时, 认为相等
if abs(self.i_value-other.i_value)<4:
return True # 返回True表示相等
else:
return False # 返回False表示不等
if __name__ == '__main__':
t1 = CTest(1)
t2 = CTest(2)
t7 = CTest(7)
print(t1 == t2) # True
print(t1 == t7) # False
作用:将可遍历对象组合为索引序列,返回enumerate对象,索引默认从0开始;
返回值:枚举对象
>>> for i, ele in enumerate([‘A’, ‘B’, ‘C’]):
... print(f‘{
i} {
ele}’)
...
0 A # 索引默认从0开始
1 B
2 C
>>> for i, ele in enumerate([‘A’, ‘B’, ‘C’], start=4):
... print(f‘{
i} {
ele}’)
...
4 A # 索引值从4开始
5 B
6 C
>>> e_obj = enumerate([‘A’, ‘B’, ‘C’], start=4)
>>> e_obj
<enumerate object at 0x****B40>
>>> list(e_obj)
[(4, ‘A’), (5, ‘B’), (6, ‘C’)]
>>> import enum
>>> @enum.unique # 这个装饰器,枚举类的值不允许重复,即不能定义两个1等等;
... class Month(enum.Enum):
... Jan = 1 # 枚举类的名称不允许重复,即不允许定义两个Jan等等
... Feb = 2
... Mar = 3
... Apr = 4
...
>>> Month
<enum ‘Month’>
获取成员(Member):
>>> Month[‘Jan’] #通过成员名称
<Month.Jan: 1>
>>> Month(1) # 通过成员值
<Month.Jan: 1>
>>> Month.Jan
<Month.Jan: 1>
通过成员获取名称(name)和值(value):
>>> JanMember = Month.Jan
>>> JanMember.name
‘Jan’
>>> JanMember.value
1
迭代枚举类
>>> for month_member in Month:
... print(f‘{
month_member.name} {
month_member.value}’)
...
Jan 1
Feb 2
Mar 3
Apr 4
>>> # 或者使用特殊属性__members__
>>> for name, month_member in Month.__members__.items():
... print(f‘{
name} {
month_member.value}’)
...
Jan 1
Feb 2
Mar 3
Apr 4
允许同一性比较,允许等值比较,不允许大小比较
>>> Month.Jan is Month.Jan # 同一性比较
True
>>> Month.Jan == Month.Jan # 等值比较
True
>>> Month.Jan < Month.Feb
TypeError: ‘<’ not support between instances of ‘Month’ and ‘Month’
作用:可以动态创建类。
以下代码定义了一个类:
class Hello(object):
def hello(self, name=‘world’):
print(‘Hello, %s’%(name))
等价于以下代码,使用type(),Python解释器遇到class定义时,就是调用type()创建class的。
def fn(self, name=‘world’):
print(‘Hello, %s’%(name))
Hello=type(‘Hello’, (object,), dict(hello=fn)) #创建Hello class
创建class对象时,type的3个参数:
对于上面两种class定义的方式,以下测试结果相同
>>> h = Hello()
>>> h.hello()
Hello, world
>>> print(type(Hello)) # Hello是通过type创建的,所以它的type是type。
<class ‘type’>
>>> print(type(h)) # h是通过Hello创建的,所以它的type是Hello。
<class ‘__main__.Hello’>
metaclass可以控制类的创建行为(创建类或修改类)
metaclass、类、实例,三者的关系:根据metaclass创建类、根据类创建实例;
定义metaclass:metaclass是类的模板,所以从type类派生;
class ListMetaclass(type): # metaclass的类名总以Metaclass结尾,以方便识别
def __new__(cls, name, bases, attrs):
attrs[‘add’] = lambda self, value: self.append(value)
return __new__(cls, name, bases, attrs)
new()方法的参数:
利用metaclass来定制类(指导类的创建方式)
class MyList(list, metaclass=ListMetaclass):
pass
使用metaclass参数,Python创建MyList时,会通过ListMetaclass.new()来创建,所以MyList这个类多了一个方法:add()
测试:
>>> L = MyList()
>>> L
[]
>>> L.add(1) # L有add()方法
>>> L
[1]
>>> L2 = list()
>>> L2.add(1) # L2没有add()方法
AttributeError: ‘list’ object has no attribute ‘add’
在ORM(Object Relational Mapping,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。
作用:classmethod是一个装饰器,它装饰的方法会变成类方法。
参数:类方法不需要self参数,但需要cls参数(表示自身类),cls参数可以用来调用类的方法、实例化对象等(?)
例1
声明类
class CTest(object):
bar = 1
@classmethod
def func0(cls): # 这个方法是类方法,需要cls参数
print(‘Calling func0’)
print(cls.bar) #可以通过cls调用类成员,但不能调用实例成员self.mem
cls().func1()#可以通过cls调用实例方法
def func1(self):
print(‘Calling func1’)
>>> #调用类方法
>>> t0 = CTest()
>>> t0.func0()
Calling func0
1
Calling func1
作用:@staticmethod是一个装饰器,它装饰的方法会变成静态方法;
参数:不需要强制的self、cls等参数;
调用:可以通过类调用、也可以通过类实例调用;
特点:静态方法不需要(不能)访问类对象或类实例的其它成员和方法(因为没有cls或self参数),只是把函数嵌入到了类中,以方便继承或组织代码;
例1:
#类
class CTest(object):
@staticmethod
def static_func():
print(‘Calling static_func’)
>>> #调用静态方法
>>> CTest.statict_func() # 通过类调用静态方法
Calling static_func
>>>
>>> t0 = CTest()
>>> t0.statict_func() #通过类实例调用静态方法
Calling static_func
例2:
#类
class CTest(object):
def foo(self, x):
print(‘Calling foo(%s, %s)’%(self, x))
@classmethod
def class_foo(cls, x):
print(‘Calling class_foo(%s, %s)’%(cls, x))
@staticmethod
def static_foo(x):
print(‘Calling static_foo(x)’%(x))
>>> #各种方法的绑定对象
>>> t0 = CTest()
>>>
>>> t0.foo # 普通方法绑定到了类的实例t0上
<bound method CTest.foo of <__main__.CTest object at 0x2ab***>>
>>>
>>> t0.class_foo # 类方法绑定到了类对象CTest()上;
<bound method CTest.class_foo of <class ‘__main__.Ctest’>>
>>>
>>> t0.static_foo # 静态方法没有绑定对象
<function CTest.static_foo at 0x2ab****>
>>> #各种方法的调用方式
>>> #调用普通方法
>>> t0.foo(1) # 通过类实例t0调用,不需要显式传递self参数;
Calling foo(<__main__.Ctest object at 0x2ab***>, 1)
>>>
>>> CTest.foo(t0, 1) # 通过类对象CTest调用,需要显示传递self参数(实例t0);
Calling foo(<__main__.Ctest object at 0x2ab***>, 1)
>>>
>>> CTest.foo(1) # 通过类对象CTest直接调用,会报告缺少参数
TypeError: foo() missing 1 required positional argument: ‘x’
>>> #调用类方法
>>> t0.class_foo(1) # 通过类实例t0调用,不需要显示传递cls参数;
Calling class_foo(<class __main__.Ctest>, 1)
>>>
>>> CTest.foo(1) # 通过类对象CTest调用,不需要显示cls参数;
Calling class_foo(<class __main__.Ctest>, 1)
>>> #调用静态方法
>>> t0.static_foo(1) # 通过类实例t0调用;
Calling static_foo(1)
>>>
>>> CTest.static_foo(1) # 通过类对象CTest调用;
Calling static_foo(1)
关键字 | 作用 | 说明 |
---|---|---|
try | 用于运行可能出错的代码。 | |
except | 如果try代码执行出错,则跳到except语句,处理错误。 | except语句可以有多个分支,分别代表不同的Error类型。 |
else | 可选。else是except语句的分支,在没发生except语句描述的错误时,执行else分支。 | |
finally | 可选。如果最后有finally语句块,则执行finally语句块。 | 无论是否有错误发生,finally都会执行。 |
执行顺序:try->except/else->finally->完成。
python的错误也是class,继承自BaseException,每个except不但捕获该类错误,还把子类也捕获了,比如ValueError会捕获UnicodeError。
import sys
try:
print(‘try…’)
a = sys.argv[1]
r = 10 / int(a)
print(‘result’, r)
except IndexError as e: # e中存储的是错误信息:list index out of range
print(‘IndexError: ’, e)
except ValueError as e:
print(‘ValueError:’, e)
except ZeroDivisionError as e:
print(‘ZeroDivisionError:’, e)
else:
print(‘No Error Occus’)
finally:
print(‘finally…’)
print(‘Done’)
测试:
$ xx.py
try...
IndexError: list index out of range # except IndexError被执行
finally # finally语句总是被执行
Done
$ xx.py 0
try...
ZeroDivisionError: division by zero # except ZeroDivisionError被执行
finally # finally语句总是被执行
Done
$ xx.py a
try...
ValueError: invalid literal for int() with base 10: ‘a’ # 触发ValueError
finally # finally语句总是被执行
Done
$ xx.py 1
try...
result: 10.0
No error occus # 如果没有触发Error,则else语句被执行
finally # finally语句总是被执行
Done
捕获可以跨层次调用:
>>> def foo(x): return 10/int(x)
>>> def bar(x): return foo(x)*2
>>> def main():
... try: bar(‘0’)
... except Exception as e: print(f‘Error: {
e}’)
... finally: print(‘finally’)
... print(‘Done’)
...
>>> main() # foo上报给bar,bar上报给main,main可以处理错误
Error: division by zero
finally
Done
如果错误没捕获,会一直上报,最终被Python解释器捕获,然后打印错误,退出程序;
也可以使用logging记录出错信息,让程序出错后不退出。
>>> import logging
>>> def foo(x): return 10/int(x)
>>> def bar(x): return foo(x)*2
>>> def main():
... try: bar(‘0’)
... except Exception as e: logging.exception(e)
... finally: print(‘finally’)
... print(‘Done’)
...
>>> main() # main捕获错误后记录到logging,并继续执行程序
ERROR:root:division by zero
Traceback (most recent call last):
File “xx.py”, line 5 in main
try: bar(‘0’)
File “xx.py”, line 3 in bar
def bar(x): return foo(x)*2
File “xx.py”, line 2 in foo
def foo(x): return 10/int(x)
ZeroDivisionError: division by zero
finally
Done
内置函数能抛出错误,我们自己写的函数也可以抛出错误。
可以自己定义错误类型,但不常用;如果可以选择Python已有的内置错误类型,尽量使用内置错误类型。
raise语句,如果没有参数,则把错误原样抛出,如果带参数(另一个错误类型),则把原来的错误转化为指定的类型。
自己定义一个错误类型
>>> class FooError(ValueError): pass
>>> def foo(s):
... n = int(s)
... if n==0:
... raise FooError(f‘invalid value: {
s}’) # raise自定义的错误
... return 10/n
...
>>> foo(‘0’)
***
***
__main__.FooError: invalid value: 0
>>> def foo(s):
...
断言失败则抛出AssertionError;
>>> def foo(s):
... n = int(s)
... assert n!=0, ‘n is zero.’
... return 10/n
...
>>> foo(0)
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
File “<stdin>”, line 3, in foo
AssertionError: n is zero.
可以通过使用-0选项来关闭assert:python -0 xx.py,这时assert语句相当于pass。
设置log输出格式:
>>> logging.basicConfig(
... level= logging.DEBUG, #设置打印级别为debug
... format = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)-8s: %(message)s',
... )
>>> logging.debug(f'log for debug')
2018-08-13 10:56:44,027 - <stdin>[line:1] – DEBUG: log for debug
>>>
>>> logging.warning(f'log for warning')
2018-08-13 10:57:57,725 - <stdin>[line:1] – WARNING : log for warning
logging的级别:NOTSSET < DEBUG < INFO < WARNING < ERROR < CRITICAL
>>> import logging
>>>
>>> logging.critical(f‘log for critical’) # 打印critical
CRITICAL:root:log for critical
>>>
>>> logging.error(f‘log for error’) # 打印error
ERROR:root:log for error
>>>
>>> logging.warning(f‘log for warning’) # 打印warning
WARNING:root:log for warning
>>>
>>> logging.info(f‘log for info’) # 打印info,默认不打印
>>> logging.debug(f‘log for debug’) # 打印debug,默认不打印
设置打印级别
>>> logging.basicConfig(level=logging.DEBUG) #设置打印级别为debug
>>> logging.info(f'log for info') # 打印info, 正常打印
INFO:root:log for info
>>>
>>> logging.debug(f'log for debug') # 打印debug, 正常打印
DEBUG:root:log for debug
设置log输出到文件和Terminal:
程序:
import logging
# 7. 创建一个logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG) # Log等级总开关
# 8. 设置log格式:
formatter=logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)-8s: %(message)s')
# 9. 创建一个fh,并添加到logger,用于写log到文件
fh = logging.FileHandler('./try.log', mode='w') # log文件名
fh.setLevel(logging.INFO) # 输出到log file的等级开关
fh.setFormatter(formater) # 使用前面定义的formatter
logger.addHandler(fh)
# 10. 创建一个ch,并添加到logger,用于写log到Terminal
ch = logging.StreamHandler()
ch.setLevel(logging.Warning) # 输出到Terminal的等级开关
ch.setFormatter(formater) # 使用前面定义的formatter
logger.addHandler(ch)
# 11. 写log
logging.critical(f'log for critical')
logging.error(f'log for error')
logging.warning(f'log for warning')
logging.info(f'log for info')
logging.debug(f'log for debug')
执行,查打印到Terminal和file的log,
Terminal”
$ xx.py # Termimal设置只打印到Warning级别的log, info和debug级别的没显示
2018-08-13 11:06:47,742 - <xx.py>[line:21] – CRITICAL: log for critical
2018-08-13 11:06:47,743 - <xx.py>[line:22] – ERROR: log for error
2018-08-13 11:06:47,744 - <xx.py>[line:23] – WARNING : log for warning
文件:
$ cat try.log # file设置只打印到INFO级别的log, debug级别的log没有显示
2018-08-13 11:06:47,742 - <xx.py>[line:21] – CRITICAL: log for critical
2018-08-13 11:06:47,743 - <xx.py>[line:22] – ERROR: log for error
2018-08-13 11:06:47,744 - <xx.py>[line:23] – WARNING : log for warning
2018-08-13 11:06:47,743 - <xx.py>[line:22] – INFO: log for info
formatter常用格式:
格式 | 说明 |
---|---|
%(levelno)s | 打印日志级别的数值 |
%(levelname)s | 打印日志级别名称 |
%(pathname)s | 打印当前执行程序的路径,其实就是sys.argv[0] |
%(filename)s | 打印当前执行程序名 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(asctime)s | 打印日志的时间 |
%(thread)d | 打印线程ID |
%(threadName)s | 打印线程名称 |
%(process)d | 打印进程ID |
%(message)s | 打印日志信息 |
调试器pdb,可以单步运行程序,或者设置断点;
程序文件test.py如下:
s = '0'
n = int(s)
print(10/n)
执行如下
$ python -m pdb test.py #在命令行调用pdb模块, 对py_test.py进行单步调试
> path/test.py(3808)<module>() #自动显示下一步要执行哪个文件的哪一行
-> s = '0' #自动显示下一步要执行的代码
(Pdb) n #用户输入n, 执行一步代码 s='0'
> path/test.py(3809)<module>() #执行代码, 然后自动显示下一步要执行哪一行
-> n = int(s)
(Pdb) n #用户输入n, 执行一步代码 n=int(s)
> path/test.py(3810)<module>() #执行代码, 然后自动显示下一步要执行哪一行
-> print(10/n)
(Pdb) n #用户输入n, 执行一步代码 print(10/n)
ZeroDivisionError: division by zero #执行代码报错
> path/test.py(3810)<module>() #代码无法执行, 仍然显示这一段代码
-> print(10/n)
(Pdb) p n #p n表示查看变量n的值
0
(Pdb) q #按q退出
程序文件如下:
import pdb
pdb.set_trace() # 运行到这会自动暂停
s = '0'
n = int(s)
print(n)
pdb.set_trace() # 在代码调试中按c会执行到下一个set_trace()
print(10/n)
执行如下
$ python test.py #正常执行程序(因为pdb在程序中导入)
> path\test.py(3809)<module>() #程序执行到第一个set_trace()断点
-> s = '0' #显示下一步要执行的代码
(Pdb) c #用户输入c, 执行到下一个set_trace()
0
> path\test.py(3813)<module>() #下一个set_trace()处的代码
-> print(10/n)
(Pdb) n #用户输入n, 执行下一步
ZeroDivisionError: division by zero #执行出错
> path\test.py(3813)<module>() #仍然停留在出错的代码处
-> print(10/n)
(Pdb) c #用户输入c, 执行到下一个set_trace()
Traceback (most recent call last): #python抛出异常
File "test.py", line 3813, in <module>
print(10/n)
ZeroDivisionError: division by zero
$ #python遇到异常后退出到命令行
使用unittest做单元测试:
import unittest # 使用unittest模块做测试
class TestStudent(unittest.TestCase): # 测试类从TestCase继承
def setUp(self): # 调用每条测试方法前会调用setUp()方法
print('\nTest Begin setUp...')
def tearDown(self): # 调用每条测试方法后会调用tearDown()方法
print('Test End tearDown...')
def test_80_to_100(self): # 以test开头的方法被认为是测试方法
s0 = Student()
self.assertTrue(s0.grade > 0) # 判断为真
self.assertEqual(s0.grade, 90) # 判断相等
with self.assertRaise(ValueError): # 判断语句会raise ValueError
s0.set_grade(101)
if __name__ = ‘__main__’:
unittest.main() # 运行测试
使用doctest做文档测试,提取注释中的代码并执行测试;
编辑文件test.py
def abs_new(n):
'''''
>>> abs_new(1)
1
>>> abs_new(-1) # 人为加入一个错误输出
2
>>> abs_new(0)
0
>>> abs_new('a')
Traceback (most recent call last):
... # 这里内容可以省略,
... # 但Traceback和ValueError的内容要与真正报错内容一样,
... # 也不能加注释
ValueError: must be a int
'''
if isinstance(n, int):
return n if n>0 else -n
else:
raise ValueError('must be a int')
if __name__ == '__main__':
import doctest
doctest.testmod() # 使用doctest测试注释中的代码,
# 如果注释正确, 则什么也不输出.
# 此处abd_new()中人为加入了个错误, 会输出错误
运行该脚本, 由于注释代码中人为加入了错误, 会报告如下信息
E:\myfile\test>python test.py
**********************************************************************
File "test.py", line 5, in __main__.abs_new
Failed example:
abs_new(-1) # 人为加入一个错误输出
Expected:
2
Got:
1
**********************************************************************
1 items had failures:
1 of 4 in __main__.abs_new
***Test Failed*** 1 failures.
E:\myfile\py_test>