Python学习笔记

文章目录

  • 1. 常用操作
    • 1.1. 数字相关操作
      • 1.1.1. 绝对值
      • 1.1.2. 数字取整
        • 1.1.2.1. 只取整数部分int()
        • 1.1.2.2. 向下取整math.floor()
        • 1.1.2.3. 向上取整 math.ceil()
        • 1.1.2.4. 四舍五入round()
      • 1.1.3. 乘方、开方
      • 1.1.4. 三角函数、对数
      • 1.1.5. 随机数
        • 1.1.5.1. 随机整数
        • 1.1.5.2. 随机浮点数
        • 1.1.5.3. 在待选中随机选择
        • 1.1.5.4. 打乱序列
      • 1.1.6. 平均值/方差/标准差
    • 1.2. 调用Tcl
      • 1.2.1. 通过eval调用tcl命令,返回值为str
      • 1.2.2. 通过setvar给变量赋值
      • 1.2.3. 通过getvar得到变量的值
    • 1.3. 时间
      • 1.3.1. time模块
        • 1.3.1.1. 求当前时间戳
        • 1.3.1.2. 求给定时间的时间戳(输入时间元组)
        • 1.3.1.3. 按指定格式显示当前时间,返回字符串
        • 1.3.1.4. 指定时间字符串,按指定格式拆分为数组
        • 1.3.1.5. 显示英文时间
        • 1.3.1.6. 进程挂起1s时间
        • 1.3.1.7. 格式化字符串
      • 1.3.2. datetime模块
        • 1.3.2.1. 初始化datatime类
        • 1.3.2.2. 得到一个指定具体时间的datetime类dt0
        • 1.3.2.3. 分别取年、月、日、时、分、秒、微秒
        • 1.3.2.4. 取date和time
        • 1.3.2.5. 取周几、日历
        • 1.3.2.6. 公元天数换算
        • 1.3.2.7. 取当前时间、零时区时间
        • 1.3.2.8. 取时间的标准str
        • 1.3.2.9. 时间戳
        • 1.3.2.10. 自定义格式时间、英文格式时间
        • 1.3.2.11. 时间元组
        • 1.3.2.12. 给定时间str,按时间格式拆开,返回datetime.datetime类
        • 1.3.2.13. datetime加减
    • 1.4. 进制转换
      • 1.4.1. 使用bin/oct/hex、int
        • 1.4.1.1. 十进制-> 其它进制
        • 1.4.1.2. 其它进制-> 十进制
      • 1.4.2. 使用format
        • 1.4.2.1. 转为二进制
        • 1.4.2.2. 转为八进制
        • 1.4.2.3. 转为十六进制
    • 1.5. 格式化字符
      • 1.5.1. 使用%
        • 1.5.1.1. 对整数指定宽度
        • 1.5.1.2. 通过变量指定宽度
        • 1.5.1.3. 对小数指定宽度
        • 1.5.1.4. 通过变量指定总宽度和小数宽度
      • 1.5.2. 使用format
        • 1.5.2.1. 通过位置映射
        • 1.5.2.2. 通过key映射
        • 1.5.2.3. 通过下标映射
        • 1.5.2.4. 填充与对齐
        • 1.5.2.5. 指定小数部分宽度
        • 1.5.2.6. 添加千分位分隔符
    • 1.6. 排序
      • 1.6.1. list内置方法sort()
      • 1.6.2. 全局方法sorted()
      • 1.6.3. key参数
    • 1.7. 处理命令行参数
      • 1.7.1. 使用步骤
      • 1.7.2. 位置参数
      • 1.7.3. flag类型的选项(选项本身无参数)
      • 1.7.4. 必选选项(选项必须声明, 不声明会报错)
      • 1.7.5. 可选选项,可带默认值
      • 1.7.6. 长选项
      • 1.7.7. 列表选项
      • 1.7.8. 未知选项
    • 1.8. 系统命令
      • 1.8.1. 使用subprocess.Popen()
      • 1.8.2. 使用os.system和os.popen
      • 1.8.3. 获取系统平台名
      • 1.8.4. 获取用户名
      • 1.8.5. 操作环境变量
        • 1.8.5.1. os.environ
        • 1.8.5.2. os.getenv()、os.putenv()
    • 1.9. json操作
      • 1.9.1. dict与str之间转换
      • 1.9.2. dict与文件间转换
    • 1.10. 处理文件
      • 1.10.1. 写文件
      • 1.10.2. 读文件
      • 1.10.3. 读取压缩文件(.gz)
      • 1.10.4. 打开文件的模式
      • 1.10.5. with…as语句
      • 1.10.6. 文件测试/文件判断
      • 1.10.7. 文件、目录、路径
      • 1.10.8. 遍历目录操作
    • 1.11. 分析程序性能cProfile
    • 1.12. 修改python搜索模块的路径集
    • 1.13. 标准输入、标准输出、标准错误
      • 1.13.1. sys.stdin()
      • 1.13.2. sys.stdout()
      • 1.13.3. sys.stderr()
    • 1.14. 使用pip安装模块
    • 1.15. python文件的打包/破解/加密
      • 1.15.1. 使用pyintaller打包代码
      • 1.15.2. pyc文件的破解
      • 1.15.3. 加密方法1: 将py文件转为pyd格式
      • 1.15.4. 加密方法2: 使用pyinstaller的—key选项
    • 1.16. 生成文件md5码
    • 1.17. 在类内部获取类名, 在函数内部获取函数名
    • 1.18. 几个函数
      • 1.18.1. map(func, *iterables)
      • 1.18.2. reduce(function, sequence[, initial])
      • 1.18.3. filter(function or None, iterable)
      • 1.18.4. zip(iterable[, iterable, ...])
      • 1.18.5. range()
      • 1.18.6. id()
      • 1.18.7. exec和eval
    • 1.19. 代码换行
    • 1.20. Unicode编码转换
    • 1.21. 三目运算
    • 1.22. 获取terminal窗口大小
  • 2. 正则表达式
    • 2.1. re的method
      • 2.1.1. re.match(pattern,string,flags=0)
      • 2.1.2. re.search(pattern, string, flags=0)
      • 2.1.3. re.findall(pattern,string[, flags])
      • 2.1.4. re.finditer(pattern, string[, flags])
      • 2.1.5. re.split(pattern, sting[, maxsplit])
      • 2.1.6. re.sub(pattern, repl, string[, count]) 替换
      • 2.1.7. re.compile(pattern,flags=0)
    • 2.2. flags的可选值:
    • 2.3. match对象的method
      • 2.3.1. group([group1, …])
      • 2.3.2. groups([default])
      • 2.3.3. groupdict([default])
      • 2.3.4. start/end/span([group])
      • 2.3.5. expand([group])
    • 2.4. 字符
    • 2.5. 预定义字符集
    • 2.6. 数量词
    • 2.7. 贪婪匹配、非贪婪匹配
    • 2.8. 边界匹配
    • 2.9. 逻辑运算
    • 2.10. 分组、命名分组、引用分组
    • 2.11. 特殊构造(构造中的括号不作为分组)
    • 2.12. 在正则表达式的patten中使用变量
  • 3. 常量、变量、运算符、表达式
    • 3.1. 常量
      • 3.1.1. 数
      • 3.1.2. 字符串
    • 3.2. 变量
    • 3.3. 运算符
      • 3.3.1. 算术运算符
      • 3.3.2. 比较运算符
      • 3.3.3. 赋值运算符
      • 3.3.4. 位运算符
      • 3.3.5. 逻辑(布尔)运算符
      • 3.3.6. 成员运算符
  • 4. 控制流
    • 4.1. if...elif...else...
    • 4.2. while...else...
    • 4.3. for...else...
    • 4.4. break 和 continue
  • 5. 函数
    • 5.1. 参数
      • 5.1.1. 几个概念
      • 5.1.2. 形式参数
      • 5.1.3. 关键字参数:
      • 5.1.4. 位置参数
      • 5.1.5. 默认参数
      • 5.1.6. 可变参数
      • 5.1.7. 关键字参数
      • 命名关键字参数
      • 参数组合
    • 返回值
    • DocStrings
    • 函数起别名
    • 函数式编程
    • 高阶函数
      • 变量可以指向函数
      • 函数名也是变量
      • 传入函数
      • 返回函数
    • 匿名函数lambda
    • 装饰器
    • 偏函数
  • 模块
    • 使用模块的好处
    • 安装第三方模块
    • 字节编译的.pyc文件
    • import
    • 模块的__name__
    • 创建自己的模块
    • dir函数
  • 数据类型
    • 列表list
      • 常用操作
      • 列表生成式
      • 生成器
        • 使用类似列表生成式的for循环实现generator
        • 使用函数实现generator: yield
      • 迭代器
    • 元组tuple
      • 元组的不可变性
      • 常用操作
    • 字典dict
    • 集合set
      • 特点:
      • 常用操作
    • 序列
      • list的切片(抓取多个元素)操作
      • 字符串切片操作
    • 对列表的引用
    • 字符串str
      • count(sub[, start[, end]])
      • find(sub[, start[, end]]), 另见rfind()
  • 面向对象
    • 名词解释
    • 访问限制
      • __xxx 变量
      • __xxx__
      • _name
    • 继承和多态
    • 获取对象信息
      • type()
      • isinstance()
      • dir()
    • 实例属性和类属性
    • self 参数
    • __init__ 方法
    • 类与对象的域(变量)
  • 面向对象高级编程
    • 使用__slots__
    • 使用@property
    • 多重继承
    • 定制类
      • __str__()
      • __repr__()
      • __iter__()
      • __getitem__()
      • __setitem__()
      • __getattr__()
      • __setattr__()
      • __call__()
      • __new__()
      • __eq__()
    • 枚举类
      • enumerate(sequence, [start=0])
      • enum
    • 使用元类
      • type()
      • metaclass
    • @classmethod
    • @staticmethod
  • 错误和异常
    • 处理错误:try..except
    • 抛出错误:raise
    • 调试
      • 使用assert
      • 使用logging
      • 使用pdb
        • 调用pdb,单步执行
        • 设置断点执行
    • 单元测试
    • 文档测试

1. 常用操作

1.1. 数字相关操作

1.1.1. 绝对值

>>> abs(-5.0)  
5.0  

1.1.2. 数字取整

1.1.2.1. 只取整数部分int()

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  

1.1.2.2. 向下取整math.floor()

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;  

1.1.2.3. 向上取整 math.ceil()

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;  

1.1.2.4. 四舍五入round()

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。

1.1.3. 乘方、开方

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型数;  

1.1.4. 三角函数、对数

三角函数

>>> 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,

1.1.5. 随机数

1.1.5.1. 随机整数

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=), 不包含stop

>>> random.randrange(10, 20, 2) # 返回[10, 20)之间的偶数,不包括stop  

1.1.5.2. 随机浮点数

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***  

1.1.5.3. 在待选中随机选择

语法: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']  

1.1.5.4. 打乱序列

语法: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]  

1.1.6. 平均值/方差/标准差

需要第三方模块 numpy

import numpy as np

f_mean = np.mean(_list) # 平均值
f_var = np.var(_list) # 方差
f_std = np.std(_list, ddof=1) # 标准差

1.2. 调用Tcl

>>> import tkinter  
>>> interp tkinter.Tcl()  

1.2.1. 通过eval调用tcl命令,返回值为str

>>> # 调用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')  

1.2.2. 通过setvar给变量赋值

>>> interp.setvar('s_tmp', 'this is a tmp string')  

1.2.3. 通过getvar得到变量的值

>>> 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'  

1.3. 时间

感觉datetime模块专业些

1.3.1. time模块

时间元组: (年,月,日,时,分,秒,周几,年中第几天,是否夏令时),手工指定时,最后3项可以指定为0;
时间戳:指定时间距1970-01-01 00:00:00总秒数,float型数;

>>> import time  

1.3.1.1. 求当前时间戳

>>> time.time() # 1531722744.1899934,float型数  

1.3.1.2. 求给定时间的时间戳(输入时间元组)

>>> t = (2018,7,16,14,38,59,0,0,0) # 时间元组,最后3项可以指定为0  
>>> time.mktime(t) # 1531723139.0,给定时间的时间戳,注意参数只有一个而不是9个  

1.3.1.3. 按指定格式显示当前时间,返回字符串

>>> time.strftime('%Y-%m-%d %H:%M:%S') # '2018-07-16 14:54:26',返回str  

1.3.1.4. 指定时间字符串,按指定格式拆分为数组

>>> 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’

1.3.1.5. 显示英文时间

>>> t = (2018,7,16,14,38,59,0,0,0)  
>>> time.asctime(t) # 'Mon Jul 16 14:38:59 2018',返回str  

1.3.1.6. 进程挂起1s时间

>>> time.sleep(1)

1.3.1.7. 格式化字符串

格式 说明
%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 当前时区的名称
%% %号本身

1.3.2. datetime模块

时间元组: (年,月,日,时,分,秒,微秒),注意这个时间元组有7个元素;
时间戳:给定的时间距离开始时间(1970-01-01 00:00:00)总秒数,float型数;
datetime模块中有一个datetime类,所有的信息都存储在这个类中

1.3.2.1. 初始化datatime类

>>> import datetime
>>> dt = datetime.datetime # 给datetime类一个别名
>>> type(dt)
# 

1.3.2.2. 得到一个指定具体时间的datetime类dt0

>>> dt0 = dt(2018,7,16,17,7,30,1)
>>> type(dt0)# 可以从dt0中取想要的数据
# 

1.3.2.3. 分别取年、月、日、时、分、秒、微秒

>>> 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

1.3.2.4. 取date和time

>>> dt0.date()# 取年月日, 返回datetime.date类
datetime.date(2018,7,16)
>>> dt0.time()# 取时间, 返回datetime.time类
datetime.time(17,7,30,1)

1.3.2.5. 取周几、日历

>>> # 取周几:
>>> dt0.weekday()#周一为0, 返回值为int
0
>>> dt0.isoweekday() #周一为1, 返回值为int
1
>>> dt0.isocalendar() # 取日历, 返回tuple,
(2018, 29,1)# 含义:(年,第几周,周几);

1.3.2.6. 公元天数换算

>>> dt0.toordinal() # 到公元元年的天数, 返回值为int
736891
>>> dt1 = dt0.fromordinal(54) # 指定到公元元年的天数,算datetime,返回datetime类
>>> dt1
datetime.datetime(1,2,23,0,0)

1.3.2.7. 取当前时间、零时区时间

>>> # 取当前时间:返回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)

1.3.2.8. 取时间的标准str

>>> dt0.isoformat() # 得到时间的标准str,返回值为str
'2018-07-16T17:07:30.1'

1.3.2.9. 时间戳

>>> dt0.timestamp() # 从datetime取时间戳: 返回float,小数位表示毫秒数
1531732050.000001
>>> dt5 = dt0.fromtimestamp(1531732650.000001) # 从时间戳取datetime: 返回datetime类
>>> dt5
datetime.datetime(2018,7,16,17,17,30,1)

1.3.2.10. 自定义格式时间、英文格式时间

>>> dt.strftime('%Y-%m-%d') # 按自定义格式返回时间str
'2018-07-16'
>>> dt.ctime() # 取英文格式的时间,返回str
'Mon Jul 16 17:07:30 2018'

1.3.2.11. 时间元组

>>> 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)

1.3.2.12. 给定时间str,按时间格式拆开,返回datetime.datetime类

>>> str='2049-12-31 23:59:59'
>>> dt6 = dt.strptime(str, '%Y-%m-%d %X')
>>> dt6
datetime.datetime(2049,12,31,23,59,59)

1.3.2.13. datetime加减

>>> 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)

1.4. 进制转换

1.4.1. 使用bin/oct/hex、int

1.4.1.1. 十进制-> 其它进制

>>> #使用bin/oct/hex,输入int,返回str;
>>> bin(50) # '0b110010',十进制 -> 二进制,返回的值为字符串,以0b开头;
>>> oct(50) # '0o62',十进制 -> 八进制,返回的值为字符串,以0o开头;
>>> hex(50) # '0x32', 十进制 -> 十六进制,返回的值为字符串,以0x开头;

1.4.1.2. 其它进制-> 十进制

>>> 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,返回十进制整数;

1.4.2. 使用format

缺点是format的参数只能是各进制的整数,不能是str;
所有需要把参数使用int()处理后再使用format, 如:
‘{n:08b}’.format(n=int(s0, base=16))

1.4.2.1. 转为二进制

>>> '{: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'  

1.4.2.2. 转为八进制

>>> '{:o}'.format(13)   #输入十进制整数,输出八进制str,:o表示转为八进制;  
'15'  
>>> '{:08o}'.format(13) #输入十进制整数,输出八进制str,:08o表示转为八进制,宽度为8,不足补0;  
'00000015'  
>>> '{:08b}'.format(0xD)#输入十六进制整数,输出八进制str;  
'00000015'  

1.4.2.3. 转为十六进制

使用大写的: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'  

1.5. 格式化字符

1.5.1. 使用%

%的常用转换类型

转换 说明 示例
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%’

1.5.1.1. 对整数指定宽度

>>> n = 5
>>> s = '%8d'%(n)# '5',默认为右对齐;
>>> s = '%08d'%(n)# '00000005',前面补0;
>>> s = '%-8d'%(n)# '5', 使用负号,左对齐;

1.5.1.2. 通过变量指定宽度

>>> w = 8
>>> n = 5
>>> s = '%*d'%(w, n)# '5',默认右对齐;
>>> s = '%0*d'%(w, n) # '00000005',前面补0;
>>> s = '%-*d'%(w, n) # '5',左对齐;

1.5.1.3. 对小数指定宽度

只指定总宽度

>>> 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;

1.5.1.4. 通过变量指定总宽度和小数宽度

>>> w_all = 5
>>> w_frac = 1
>>> '%0*.*f'%(w_all, w_frac, n) # '003.1' 小数部分1bit,整数部分3bit,小数点1bit,共5bit;

1.5.2. 使用format

format是python2.6后增加的功能

1.5.2.1. 通过位置映射

>>> '{} and {}'.format('hello', 'world') # 'hello and world' ,不指定位置,按默认顺序;
>>> '{0} and {1} or {0}'.format('hello', 'world') # 'hello and world or hello' ,指定位置,位置可以重复使用;

1.5.2.2. 通过key映射

>>> '{name} is {age} years old'.format(age=18, name='Jim') # 'Jim is 18 years old'

1.5.2.3. 通过下标映射

>>> 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'

1.5.2.4. 填充与对齐

语法: {:[填充符][对齐方式][宽度]}
对齐方式:’^‘表示居中,’<‘表示左对齐,’>'表示右对齐;
宽度: 通过数字或变量指定;

>>> '{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

1.5.2.5. 指定小数部分宽度

>>> '{:.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',通过变量指定宽度;

1.5.2.6. 添加千分位分隔符

>>> '{:,}'.format(1234567.89123) # 1,234,567.89123 , ':,'表示添加千分位分隔符;

1.6. 排序

1.6.1. list内置方法sort()

语法: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  

1.6.2. 全局方法sorted()

语法: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分排在后;  

1.6.3. key参数

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']  

1.7. 处理命令行参数

1.7.1. 使用步骤

使用步骤如下:

# 导入模块  
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之后的内容;  

1.7.2. 位置参数

# 位置参数可以实现类似于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)  

1.7.3. flag类型的选项(选项本身无参数)

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  

1.7.4. 必选选项(选项必须声明, 不声明会报错)

>>> 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  

1.7.5. 可选选项,可带默认值

-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'>  

1.7.6. 长选项

>>> 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']  

1.7.7. 列表选项

nargs=’+’ 表示当前选项可以有多个参数, 这些参数组成一个list, 例子见上一节.

1.7.8. 未知选项

如果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']  

1.8. 系统命令

1.8.1. 使用subprocess.Popen()

调用命令:

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())  

1.8.2. 使用os.system和os.popen

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

1.8.3. 获取系统平台名

Windows对应’nt’,Limux/Unix对应’posix’;

>>> os.name
'posix'

1.8.4. 获取用户名

>>> import getpass
>>> getpass.getuser()
'g004440**'

1.8.5. 操作环境变量

1.8.5.1. os.environ

>>> 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'

1.8.5.2. os.getenv()、os.putenv()

注意:通过putenv()设置的环境变量,使用getenv()无法获取,使用os.environ[]也无法获取(即没有修改os.environ的值),它只能是默默地修改了环境变量?
os.getenv(key, default=None) 获取环境变量
os.putenv(name, value, /) 设置环境变量name=value,value必须是strbytes或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

1.9. json操作

1.9.1. dict与str之间转换

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'>

1.9.2. 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.10. 处理文件

1.10.1. 写文件

例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.10.2. 读文件

例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中;  

1.10.3. 读取压缩文件(.gz)

使用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)  

1.10.4. 打开文件的模式

模式 描述
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
w 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
a 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
rb r的二进制模式
wb w的二进制模式
ab a的二进制模式
r+ r的读写模式(不会创建不存在的文件从顶部开始写会覆盖之前此位置的内容,如果先读后写,则会在文件最后追加内容。)
w+ w的读写模式(如果文件存在,则覆盖整个文件,如果不存在,则创建。要close 之后才算完成写入)
a+ a的读写模式(可读可写,从文件顶部读取内容,从文件底部添加内容,不存在则创建)
rb+ r的二进制+读写模式
wb+ w的二进制+读写模式
ab+ a的二进制+读写模式

1.10.5. with…as语句

可以使用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.10.6. 文件测试/文件判断

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;

1.10.7. 文件、目录、路径

获取程序所在目录:

#假设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'  

1.10.8. 遍历目录操作

目录结构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  

1.11. 分析程序性能cProfile

一个函数

>>> 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):每个函数调用的具体信息;

1.12. 修改python搜索模块的路径集

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"

1.13. 标准输入、标准输出、标准错误

1.13.1. sys.stdin()

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'

1.13.2. sys.stdout()

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('')
...
*****
*****
*****
*****
*****

1.13.3. sys.stderr()

可以把print的内容输出到标准输出

>>> import sys
>>> print('abc', sys.stderr)
abc <_io.TextIOWrapper name='' mode='w' encoding='ANSI_X3.4-1968'>

1.14. 使用pip安装模块

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 install –target= e:\myfile\site_packages pillow
需要把这个目录添加到PYTHONPATH或sys.path
好像在linux下安装不成功.
卸载模块 pip3 uninstall pycryptodome
升级模块 pip3 install –upgrade pycryptodome
检查待更新 pip3 list –outdated
查看已安装 pip3 show –files pycryptodome

如果在工作站内自己安装模块, 但没有连网, 也没有工作站权限,

  1. 先在有网的地方下载:
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
  1. 有时下载到的文件后缀不上whl, 而是gz, 也可以安装:
pip3 install --user pycrypto-2.6.1.tar.gz

1.15. python文件的打包/破解/加密

1.15.1. 使用pyintaller打包代码

pyinstaller abc.py # 要打包的top代码
    -F         # 只生成单个的exe文件, 缺点是运行exe时要先解压.
    -w         # 不生成命令行窗口.
    --debug    # 生成带debug信息的exe文件.
    --clean    # 运行前清理编译的临时文件;
    --icon     # 工具的top图标文件
    --distpath # 输出的exe存放的目录
    --workpath # 编译的临时文件存放的目录
    --hidden-import # 隐含import的模块, 比如自定义/第三方的import模块等
    --key      # 加密密钥

1.15.2. pyc文件的破解

pyinstaller打包的py文件会转为pyc格式, 但这种文件很容易破解出源码.
操作如下:

  1. 下载pyinstxtractor.py;
  2. 从exe中获取pyc文件: python pyinstxtractor.py …/dist/xx.exe;
  3. 在生成的xx.exe_extracted目录中找到需要破解的pyc文件: yy.pyc;
  4. 使用在线反编译工具https://tool.lu/pyc读入pyc, 得到源码.

1.15.3. 加密方法1: 将py文件转为pyd格式

pyd是动态链接库文件, 本质上与dll文件相同, 安全性比pyc文件高很多. 所以这种方式不算真正意义上的加密, 而是一个编译过程.
操作如下:

  1. 安装easycython模块 pip install easycython
  2. 使用easycython命令将py转pyd: easycython xx.py, 会在同一目录生成xx.pyd(如果是64位系统生成的文件名为xx.cp36-win_amd64.pyd);
  3. 将xx.cp36-win_amd64.pyd重命名为xx.pyd;
  4. 使用pyinstaller打包(由于xx.py和xx.pyd在同一目录, pyinstaller会优先打包pyd), 打包时添加—hidden-import xx选项.
  5. 生成的打包文件中会包含xx.pyd;
  6. 注意: 如果打包时使用了—key选项, 但打包的模块是pyd格式的, 则pyd文件不会被加密, 只是打包.

1.15.4. 加密方法2: 使用pyinstaller的—key选项

操作如下:

  1. 安装Visual Studio 2017 Community, 需要用到它的c语言编译功能;
  2. 安装PyCrypto模块: pip install PyCrypto –i https://pypi.douban.com/simple, 需要调用该模块;
  3. 运行pyinstaller --key 0123456789 –F [other options]
  4. 对生成的exe破解时, 会报告Fail to decompress xx, probably encrypted. 破解输出的目录中生成的是xx.pyc.encrypted文件, 不再是xx.pyc.
  5. 注意: 只能加密py文件, 如果加密的模块是pyd文件, 则pyd文件会直接打包, 不会被加密.
  6. 听说密钥也一并打包到输出文件中了, 所以好像也不安全. 感觉还是pyd靠谱些, 至少差不多是C编译后的文件.

安装或使用过程可能遇到的问题:

  1. 安装PyCrypto模块时, 报告错误:
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. pycryptodome模块引起的问题. 网上有帖子说PyCrypto模块已经停止维护, 推荐安装pycryptodome模块, 但这个模块安装后可能会引发一些错误.
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  

解决方法:

  1. 只安装PyCrypto模块;
  2. 或者PyCrypto模块和pycryptodome都安装;
  3. 两个模块都安装后, 想卸载某一个模块时, 不能直接卸载其中一个, 只能两个全部卸载, 然后重新安装需要保留的模块.

加密效果如这个网址上说的https://blog.csdn.net/mutex86/article/details/45367143:

1.16. 生成文件md5码

没办法直接生成文件的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得到的结果一致;

1.17. 在类内部获取类名, 在函数内部获取函数名

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  

1.18. 几个函数

1.18.1. map(func, *iterables)

参数: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']

1.18.2. reduce(function, sequence[, initial])

参数: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

1.18.3. filter(function or None, iterable)

参数: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]

1.18.4. zip(iterable[, iterable, …])

语法: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)

1.18.5. range()

语法:
?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]

1.18.6. id()

返回一个对象的序号,转成16进制后即内存地址。

>>> class Test():
...pass
...
>>> t0 = CTest()
>>> t0
<__main__.CTest object at 0x00328D710>
>>> id(t0)
53008144
>>> '{:x}'.format(id(t0)) # id()返回的为10进制数,转为16进制后为内存地址;
328d710

1.18.7. exec和eval

exec执行字符串形式的代码(python语句,做某事),返回None

>>> exec('a = 6') # 相当于把a=6这条语句放在此处执行
>>> a
6

eval执行字符串形式的表达式,返回表达式的值

>>> a = eval('2*3')
>>> a
6
>>> b = eval('a==6')
>>> b
True

1.19. 代码换行

物理行:在编写程序时所"看见"的行。
逻辑行:Python"看见"的单个语句。

Python假定每个物理行对应一个逻辑行。
可以使用分号把多个逻辑行写到一个物理行中;
可以使用续行符在多行物理行中写一个逻辑行;

还有一个暗示的行连接:如果物理行使用了括号(圆括号、方括号或花括号),且括号没有闭合,则认为后面的行仍然是同一个逻辑行。

1.20. Unicode编码转换

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代替。

1.21. 三目运算

通过if … else实现

>>> Z = I0 if S==False else I1

通过and … or 实现

>>> def sel(S, I0, I1):
...return (S and [I1] or [I0])[0]

1.22. 获取terminal窗口大小

>>> 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, 得到的是窗口行占的字符数

2. 正则表达式

import re

2.1. re的method

2.1.1. re.match(pattern,string,flags=0)

说明:

  1. 从起始位置扫描string,返回match对象,match对象内容为第一个成功的匹配;(匹配不成功返回None)
  2. 与search的区别是,match要从开头扫描,search可以从任意位置开始扫描;
  3. 可能通过括号捕获内容,然后使用match对应.groups()/group()来访问捕获内容;
>>> 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.  

2.1.2. re.search(pattern, string, flags=0)

说明:

  1. 扫描string,返回match对象,match对象内容为第一个成功的匹配;(匹配不成功返回None)
  2. 与match的区别是,match要从开头扫描,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。  

2.1.3. re.findall(pattern,string[, flags])

说明:搜索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']  

2.1.4. re.finditer(pattern, string[, flags])

说明:搜索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  

2.1.5. re.split(pattern, sting[, maxsplit])

用正则表达式切分字符串,返回切分后的列表;
比使用固定字符(str.split(‘ ’))更灵活;

re.split(r’[\s\,\;]+, ‘a, b; ; cd’) # [‘a’,’b’,’c’,’d’]

2.1.6. re.sub(pattern, repl, string[, count]) 替换

  1. 参数说明:
     pattern,表示要匹配的pattern
     repl,表示要将pattern替换成的目标字符串
     string,原字符串
     count,替换的个数
     flags,是个什么东西?

  2. 基本替换:

>>> s0 = ‘hello 123 world 456>>> s1 = re.sub(r‘\d+,222, s0) # 将s0字符串中的数字串替换为‘222’;  
‘hello 222 world 222
  1. 逆向引用(在pattern和repl中使用):
    例1:
>>> 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中对命名组的逆向引用。  
  1. 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.  
  1. count,替换的次数
>>> #接上一个例子,要求只替换两次,第三个匹配不替换:  
>>> s1 = re.sub(r’(?P<num>\d+), _add1, s0, 2)  
'hello 234, world 567 696' # s0中的数字每一位都加1, 但第3个匹配不替换.  

2.1.7. re.compile(pattern,flags=0)

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个括号截获的子串的索引;  

2.2. flags的可选值:

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对象;  

2.3. match对象的method

match对象通过调用这些方法,可以处理match匹配的内容。

2.3.1. group([group1, …])

作用:获取一个或多个分组截获的字符串;
例:

>>> 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;  

2.3.2. groups([default])

作用:以元组形式返回分部分组截获的字符串,相当于调用group(1,2, … last); default表示没有截获字符串的组以这个值替代,默认为None。
例:

>>> m0 = re.match(r’^(\d+)-(\d+),010-12345abc) # 返回一个match对象  
>>> m0.groups() # (‘010’, ‘12345’),group(1)和group(2)组成的元组  

2.3.3. groupdict([default])

作用:返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上;
例:暂无

2.3.4. start/end/span([group])

作用:

  1. start([group]):返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0;
  2. end([group)):返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0;
  3. span([group]):返回(start(group), end(group));
    例:
>>> 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;  

2.3.5. expand([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’  

2.4. 字符

  1. 一般字符:匹配自身;
  2. 点号(.):匹配任意字符(换行符\n除外,在DOTALL模式也能匹配换行符);
  3. 反斜杠():转义字符,改变字符原意,如匹配可以使用*或[],匹配\可以使用\;
  4. 中括号([…]):字符集,可以匹配字符集中列出的任意一个字符;

可以逐个列出:[abc]
可以给出范围:[a-c]
可以使用定义字符集:[\w]
可以取反:[^abc],不是abc的其它字符

特殊字符在字符集中要转义:[^ - [ ] ]

2.5. 预定义字符集

字符集 说明
\d 数字,等价于[0-9];
\D 非数字,等价于[^\d];
\s 空白字符,等价于[<空格>\t\r\n\f\v];
\S 非空白字符,等价于[^\s];
\w 单词字符,等价于[_A-Za-z0-9];
\W 非单词字符,等价于[^\w];

2.6. 数量词

表示匹配字符重复的次数。

数量词 说明
* 匹配前一个字符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次;

2.7. 贪婪匹配、非贪婪匹配

正则默认为贪婪匹配,进行非贪婪匹配需要使用?;

  1. *? 匹配前一个字符0次;
  2. +? 匹配前一个字符1次;
  3. ?? 匹配前一个字符0次;
  4. {m,n}? 匹配前一个字符m次;

例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个数字;

2.8. 边界匹配

  1. ^ 匹配字符串开头(多行模式时匹配每一行开头);
  2. $ 匹配字符串末尾(多行模式时匹配每一行末尾);
  3. \A 仅匹配字符串开头;
  4. \Z 仅匹配字符串末尾;
  5. \b 匹配单词字符串(\w)边界;
  6. \B 相当于[^\b],非单词字符串边界;

2.9. 逻辑运算

只有“或”逻辑,没有“与”逻辑;
例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’

2.10. 分组、命名分组、引用分组

说明:

  1. (…) :括起来的内容作为普通分组,以数字编号(每遇到一个左括号,编号加1);
    说明1:分组做为一个整体,后面可以接数量词,表示这个分组出现多次;
    说明2:分组中可以使用表达式(|),这个“或”仅在该组中有效;
  2. (?P…) :括起来的内容做为命名分组,作用类似于普通分组。
    区别是这个组还被指定了一个额外的名字;
  3. :引用编号为的分组;
  4. (?P=name) :引用名称为的分组;

例:

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

2.11. 特殊构造(构造中的括号不作为分组)

特殊构造 说明
(?:…) (…)的不分组/不捕获版本,这个地方的左括号不计入编号;
可以用于把多个字符做为一个整体使用‘|’或使用数量词,同时不影响其它分组的编号。如:
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可以省略。

2.12. 在正则表达式的patten中使用变量

>>> import re
>>> a =1234>>> b =1234567>>> re.match(f’{
     a}, b)
<_sre.SRE_Mathc object; span=(0, 4), match=1234>

3. 常量、变量、运算符、表达式

3.1. 常量

3.1.1. 数

数本身不可改变,包括:

  1. 整数,int(比如2),Python3中没有Long类型,python3 int没限制大小,可以当作Long使用;
  2. 浮点数,float(小数3.23、幂记法的数52.3E-4);
  3. 复数,complex(比如-5+4j,complex(a, b));

3.1.2. 字符串

  1. 字符串不可变;
  2. 可使用单引号(原样保留字符串内的字符)、双引号、三引号(可以指示一个多行的字符串,可以是’’’或“““)创建;
  3. 行尾的\表示续行,在代码中看起来是两行,实际等价于一行内容;
  4. 自然字符串:加上r或R前缀,表示不需要转义处理,如r’new lines are indicated by \n’;(类似于perl中的单引号,不转义)
  5. Unicode字符串:加上u或U前缀,表示非英文的字符串,如u’a Unicode string’;这是书写国际文本的标准方法。
  6. 字符串级连:相邻放置即可,如’What’s’ ’ your name’会被转为’What’s your name’,注意在your前自己加一个空格,注意your空格之前是两个单引号,不是一个双引号;
  7. 使用正则表达式时,最好使用自然字符串,否则会增加很多\。比如r’\1’,如果使用普通字符串,要写为’\1’;

3.2. 变量

  1. 变量名不能以数字开头、变量名大小写敏感;
  2. 数据类型:基本的数据类型是数、字符串,(还可以使用类创建自己的类型);
  3. python把程序中用到的任何东西(数、字符串、函数等)都称为对象。
  4. 注意,与C不同,Python不需要声名,不需要定义数据类型,Python会自己判断其类型;
  5. 注意,与Perl也不同,Python不能自动转换数据类型;
  6. 数据类型转换:
转换函数 说明
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  

3.3. 运算符

3.3.1. 算术运算符

算术运算符通常由左向右结合,即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
* 乘方

3.3.2. 比较运算符

运算符 作用 说明
== 等于 可用于数、str、list等
!= 不等于
<> 不等于,类似于!=
>、>= 大于、大于等于
<、<= 小于、小于等于

3.3.3. 赋值运算符

赋值运算由右向左结合,即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
-= 减等
*= 乘等
/= 除等
%= 模等
**= 幂等
//= 整除等

3.3.4. 位运算符

运算符 作用 说明
& 按位与 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  

3.3.5. 逻辑(布尔)运算符

运算符 作用 说明
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  

3.3.6. 成员运算符

运算符 作用 说明
in 包含成员 在指定的序列中找到值,返回True
not in 不包含成员

4. 控制流

4.1. if…elif…else…

if xxx:
    a
elif yyy:
    b
else:
    c

4.2. while…else…

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  

4.3. for…else…

#range返回一个列表,不包含结束值。  
for i in range(1,5):  
    print(i)  
else:  
    print('over')  

for对应的else语句类似于while对应的else语句,在for循环正常结束的情况下会被执行,具体解释见while处的解释。

4.4. break 和 continue

break: 终止循环
continue: 进入下一轮循环

5. 函数

5.1. 参数

def add(a,b=0):  
    c = a+b  
    global d, e  
    d = a*b  
    e = a+b  

5.1.1. 几个概念

  1. 形式参数:a和b;
  2. 局部变量:c,在函数内部声明的变量;
  3. 全局变量:d和e

5.1.2. 形式参数

  1. 形参可以定义默认参数 def func(a, b=5, c=10)
  2. 只有在形参表末尾的那些参数可以有默认参数值,例如def func(a, b=5)是有效的,但是def func(a=5, b)是无效的;

5.1.3. 关键字参数:

  1. 通过命名为参数赋值的一种参数,不用考虑位置;
  2. 调用函数时可以使用关键字参数:func(25, c=24);
  3. 使用关键字参数的好处:1)不用担心参数顺序;2)如果其它参数都有默认值,我们可以只给我们想要的参数赋值;

5.1.4. 位置参数

传入的值按照位置顺序依次赋给函数的形参。比如power(x, n)

5.1.5. 默认参数

可以在定义函数时,把后面的参数赋一个默认值,如果调用函数时不指定,则使用默认值;

def power(x, n=2):
    pass

注意:

  1. 必选参数(位置参数)在前,默认参数在后;否则报错;
  2. 函数有多个参数时,变化大的参数放前面,变化小的参数放后面,变化小的做默认参数;

5.1.6. 可变参数

传入的参数个数是可变的:

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元素变成可变参数传入

5.1.7. 关键字参数

可变参数允许传入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作为关键字参数;使用一个*号与前面的参数隔开;

  1. 命名关键字参数必须传入参数名(使用’city’=’Chengdu’这种方式传参),没有参数名时,会被认为是位置参数,可能导致Error;
  2. 命名关键字参数可以在定义函数时给默认值,调用时就可以不传这个参数了。
>> 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种参数可以任意组合使用, 参数定义的顺序必须为:

  1. 必选参数
  2. 默认参数
  3. 可变参数
  4. 命名关键字参数
  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个参数。

返回值

  1. 使用return从函数返回(跳出),也可以返回一个值。
  2. 程序默认返回None
  3. return可以返回多个值(实际上是返回一个元组,省略括号,直接赋值给多个变量)
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))  

DocStrings

  1. 可以在函数中嵌入说明语句.
  2. 使用三引号可以跨行写说明,不需要续行符
  3. 使用__doc__方法,可以调用到说明语句。
>>> 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()、都是高阶函数

返回函数

高阶函数除了可以接受函数做为参数外,还可以把函数做为结果值返回;

匿名函数lambda

  1. lambda 定义了一个匿名函数
  2. lambda 并不会带来程序运行效率的提高,只会使代码更简洁。
  3. lambda 只能有一个表达式,不用写return,返回值就是该表达式的值;
  4. 如果可以使用for…in…if来完成的,坚决不用lambda。
  5. 如果使用lambda,lambda内不要包含循环,如果有,宁愿定义函数来完成,使代码获得可重用性和更好的可读性。
>>> 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***  

偏函数

  1. 语法:functools.partial(函数对象, *args, **kwargs)
  2. 参数:函数对象、位置参数、命名参数

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)  

模块

使用模块的好处

  1. 提高代码可维护性;
  2. 编写代码不必从0开始;
  3. 避免函数名、变量名冲突;相同的名字可以存在不同的模块中;

假设模块名称也冲突了,可以通过包来组织模块,避免冲突。
方法是选择一个顶层包名,按目录mycomp存放;这样模块名就成了mycomp.abc
包目录下必须有一个__init__.py文件,否则Python把目录当普通目录,而不是一个包。

安装第三方模块

字节编译的.pyc文件

作用:比输入模块要快。

import

from sys import argv
from sys import *
可以把sys模块的使用的名字输入。
建议使用import sys,程序易读且避免名称冲突。

模块的__name__

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.03.	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'  

dir函数

作用:列出模块定义的标识符(三种:函数、变量、类)
参数:提供模块名时,返回该模块的标识符,不提供模块名时,返回当前模块的标识符。

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__’]  

数据类型

列表list

常用操作

注: 可以通过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中的字符串改成小写;

生成器

使用类似列表生成式的for循环实现generator

列表容量有限,一边循环一边计算生成列表,这种方式叫: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,就没有内容了;

使用函数实现generator: yield

如果一个函数定义中包含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. isinstance(iter([]), collections.Iterator) # True

元组tuple

元组的不可变性

元组不可变,主要是为了代码安全;
如果元组的元素是列表,则元组不可变决定了这个元素要一直指向这个列表,但列表的内容却是可以改变的;

常用操作

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 # 当只定制一个元素时,可以不使用圆括号。  

字典dict

使用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  

集合set

特点:

  1. set和dict类似,是一组key的集合,但不存储value;
  2. set中的key不能重复,所以set中没有重新的key值;即可以认为是数学上无序、无重复元素的集合;

常用操作

常用方法

函数名 用法 说明
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. 通过list创建set:s = set([1,2,3,4,3]); #重复的3只会留下1个;
  2. 删除元素:s.remove(4);
  3. 两个set可以做交集、并集的操作:
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] # 取倒数第二个元素

list的切片(抓取多个元素)操作

语法: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]为例说明:

  1. 结束位置j不包含在返回在的list中,
    a[1:2]只包含一个元素a[1],
    a[1:1]返回空数组[];
  2. j和k都可以省略,但i和j中间的冒号不能省略,
  3. j和k中间的冒号(在k不声明时)可以省略。
  4. list的idx值不能超出list的索引范围,比如a[5]会报告list idx out of range;
  5. i可以超出索引范围,返回空数组;比如a[5:],a[-6::-1];
  6. j可以超出索引范围,超出的范围不做处理;比如a[3:6]返回[3,4],a[-2:6]返回[3,4](a[-2]即a[3]);
  7. j可以超出i的范围(k>0时,ji),返回值为空数组[];比如a[3:1],a[-2:-4],a[-4,-2,-1];

常用取元素(以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. 替换元素,k==1:
1.	>>> a[0:2] = [‘a’, ‘b’, ‘c’]  
2.	>>> a  
3.	[‘a’, ‘b’, ‘c’, 2, 3, 4]  

说明:将第0~1个元素替换为[‘a’, ‘b’, ‘c’], 注意:k=1时,切片元素数目和替换元素数目可以不相等。

  1. 删除元素,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. 替换元素:k!=1(包括k==-1的情况):
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. 反向替换,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

字符串都是str类的对象。
help(str)可以查看str类的完整信息。

count(sub[, start[, end]])

作用:返回sub在string里出现的次数。如果指定了start或end,则返回指定范围内出现的次数。
参数:

  1. sub:搜索的子字符串;
  2. start:字符串起始搜索的位置,默认为0;
  3. end:字符串结束搜索的位置,默认为len(sting),开集,不包括这个idx;

实例:

1.	>>> s =1abc2abc3abc2.	>>> 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  

find(sub[, start[, end]]), 另见rfind()

作用:检测字符串是否包含子串str;

返回值:

  1. string包含sub时,返回索引值(find从左边找,rfind从右边找)
  2. string不包含sub时,返回-1;

参数:

  1. sub,子串;
  2. start,起始位置,默认为0;
  3. end,结束位置,默认为len(string);

实例:

1.	>>> s0 = ‘This isa string’  
2.	>>> s1 =is3.	>>> 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  

面向对象

名词解释

名词 解释
类创建一个新类型;是一个抽象的模板;
对象/实例 类的实例;每个对象拥有相同的方法,但数据可能不同;
属于一个类或对象的变量,用于存储数据;有两种类型:实例变量、类变量;
方法 属于一个类的函数;
属性 域和方法合称为属性;
实例变量 属于每个实例(类的对象)的域;
类变量 属于类本身的域;

访问限制

__xxx 变量

实例的变量如果以双下划线开头,表示是一个私有变量;外部不能访问,只能通过添加方法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(),仍然返回原本的值。  

xxx

以双下划线开头且以双下划线结尾的,是特殊变量,可以外部访问,不是private;

_name

以单下划线开头的实例变量,外部可以访问,但要视为private,不要随意访问;

继承和多态

子类(subclass):继承得到的新的class;
基类、父类、超类(base class、super class):被继承的class;

继承的好处:1)子类获得了父类的全部功能;2)多态;

如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类,反过来不行;可以使用isinstance判断类型,如:isinstance(c, Animal)
当我们需要对函数传入Dog、Cat、Tortoise时,函数参数只需要接收Animal类型就可以了。
对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数;

获取对象信息

type()

type()函数用来获取对象类型

  1. type(123) #
  2. type(d) # main.Dog’>
  3. type(abs) # 内置函数

type()返回的是class类型,所以相同类型的比较会返回True, 直接与类型比较也返回True:

  1. type(123) == type(456) # True
  2. type(123) == int # 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()

语法: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()函数用于获取一个对象的所有属性和方法;比如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 参数

类的方法是函数,与普通函数的区别是:类的方法必须要有一个额外的参数,这个参数是方法的第一个参数,但是,在调用这个方法的时候不用为这个参数赋值。这个参数指对象本身,一般命名为self。例如:
MyObject = MyClass()
MyObject.method(arg1, arg2) # python会自动转为MyClass.method(MyObject, arg1, arg2),注意,是用MyClass调用的method,而不是MyObject。
所以,即使一个方法没有参数,还是得给这个方法定义一个self参数;

init 方法

__init__方法不需要专门调用,只需要在创建类的实例时把参数写在圆括号内就可以传递给__init__方法。

类与对象的域(变量)

  1. 有两种类型的域:类的变量和对象的变量;
  2. 类的域与对象的域,本质上都是普通变量,它们与类和对象的名称空间绑定,只在类和对象的前提下有效。
  3. 类的变量:由类的所有对象(实例)共享使用,只有一个拷贝,当某个对象对它做改动时,这个改动会反映到其它所有实例上;使用MyClass.xxx赋值。
  4. 对象的变量:由类的每个对象(实例)拥有,每个对象都有一份拷贝,不是共享的。同一个类的不同对象中,虽然对象的变量有相同的名称,但它们互不相关。使用self.xxx赋值。
  5. docstring对类和方法同样有用;可以使用Person.__doc__和Person.sayHi.__doc__访问;
  6. __del__方法:在对象消逝(对象不再被使用,其占用的内存返回给系统)的时候被调用;也可以直接指明运行__del__方法;
  7. python中所有的类成员(包括数据成员)都是公共的,所有的方法都是有效的。只有当使用的数据成员名称以双下划线前缀时(如 __privatevar),Python自动把它当作私有变量。

面向对象高级编程

数据封装、继承、多态只是面向对象程序设计的基础概念,还有高级特性,比如:
多重继承、定制类、元类等;

使用__slots__

指定一个元组,当前类只能绑定该元组的元素作为属性。

>>> 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

@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做法:

  1. 使用@property装饰器,将get方法转变属性;
  2. 使用@{name}.setter,将set方法变成给属性赋值;
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 C20
from C10 C11
class C10
from C00
class C11
from C00
class C00
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’>, <classobject>)  
>>> 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"

  1. c.run(), 调用C20.run()
  2. C20.run()第一行,super(C20, self).run(),mro[1],调用C10.run()
  3. C10.run()第一行,super(C10, self).run(),mro[2],调用C11.run()
  4. C11.run()第一行,super(C11, self).run(),mro[3],调用C00.run()
  5. C00.run()没有super,打印’run C00’, C00.run()执行完毕
  6. C11.run()第二行,打印‘run C11’,C11.run()执行完毕
  7. C10.run()第二行,打印‘run C10’,C10.run()执行完毕
  8. C20.run()第二行,打印‘run C20’,C20.run()执行完毕

定制类

str()

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()

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()

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  

getitem()

使用__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__()可实现“按下标取元素“,但不能处理切片、不能“按下标赋值”、不能删除元素,这些都可以通过添加相应的方法来完成。

拦截对[‘’]方式的属性调用

setitem()

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]  

getattr()

当调用不存在的属性时,正常情况下会报告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

setattr()

拦截属性的赋值语句, 即 obj.xx = yy

class F(object):
def setattr(self, key, value):
self.dict[key] = value

call()

使用__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()

new()与__init__()的区别:

  1. new()方法先调用, init()方法后调用.
  2. new()是class级别的方法, 用于控制一个新instance的生成过程.
  3. new()需要一个参数cls, (但又不需要声明它是@classmethod).
  4. new()必须返回实例化出来的实例.
  5. init()是instance级别的方法, 用于初始化一个新实例.
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),

  1. 首先使用参数name和age来执行Person.new(name, age), new()会返回Person类的一个实例, 通常是使用这种方式: super().new(cls, …).
  2. 然后使用__new__()返回的实例调用__init__()方法.

new()方法的使用场景:

  1. 继承不可变的class(比如str, int, tuple等)时. 如永远是正数的整型.
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  
  1. 实现单例模式, 通过__new__()方法返回实例.

eq()

重新定义类的==行为.

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(sequence, [start=0])

作用:将可遍历对象组合为索引序列,返回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’)]  

enum

>>> 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’  

使用元类

type()

作用:可以动态创建类。

以下代码定义了一个类:

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个参数:

  1. class名称;
  2. 继承的父类集合(如果只有一个父类,tuple单元素需要一个逗号);
  3. class的method名称与函数绑定(这里把函数fn绑定到method hello上);

对于上面两种class定义的方式,以下测试结果相同

>>> h = Hello()  
>>> h.hello()  
Hello, world  
>>> print(type(Hello)) # Hello是通过type创建的,所以它的type是type。  
<classtype>  
>>> print(type(h)) # h是通过Hello创建的,所以它的type是Hello。  
<class ‘__main__.Hello’>  

metaclass

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()方法的参数:

  1. 待创建的类对象;
  2. 类名字;
  3. 类的父类集合;
  4. 类的方法集合;

利用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:listobject has no attribute ‘add’  

在ORM(Object Relational Mapping,对象-关系映射,把关系数据库的行为映射为一个对象,即一个类对应一个表)框架中,类只能动态定义,因为类的定义取决于表的结构,只有使用者才能根据表的结构定义出对应的类。

@classmethod

作用: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

作用:@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 如果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  

抛出错误:raise

内置函数能抛出错误,我们自己写的函数也可以抛出错误。
可以自己定义错误类型,但不常用;如果可以选择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):  
...  

调试

使用assert

断言失败则抛出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。

使用logging

设置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

调试器pdb,可以单步运行程序,或者设置断点;

调用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>  

你可能感兴趣的:(笔记,python,python)