美胜丑,显胜隐,简胜杂,杂胜乱,平胜陡,疏胜密
简单问题的方法
充分体现 Python 自身特色的代码风格
避免只用大小写区分不同对象
避免使用容易引起混淆的名称
Python底层为C语言实现
1.缩进与{}
不要混用tab和空格
2.C语言中单引号代表一个字符,实际对应于编译器所采用的字符集中的一个整数值
双引号表示字符串,默认以"\0"结尾
3.三元操作符
C?X:Y
Python支持的语法为 结果A if 条件 else 结果B
4.switch...case
Python更灵活
块注释、行注释以及文档注释(docstring)
函数定义或者类定义之间空两行,需要语义分割的地方空一行
避免过长的代码行
函数优点:最大化的代码重用和最小化的代码冗余
提高程序的健壮性
增强可读性,减少维护成本
设计尽量短小,嵌套层次不宜过深,最好控制在3层以内
声明应当合理、简单、易于使用,参数个数不宜过多
考虑向下兼容
一个函数只做一件事,尽量保证函数语句粒度的一致性
不要在函数中定义可变对象作为默认值
使用异常替换返回错误
保证通过单元测试
1.命名风格提醒使用者该变量代表的意义为常量
常量所有字母大写,下划线连接各个单词
2.自定义的类实现常量功能
命名全部为大写+值一旦绑定便不可修改
将存放常量的文件命名为 constant.py
,并在其中定义一系列的常量。
assert主要为调试程序服务,方便快速地检查异常和不恰当的输入
__debug__的值默认为True,且为只读
断言会有代价,会对性能产生一定影响,断言只在调试模式下启用
禁用断言的方式是在运行脚本的时候加上-0标志,并不优化字节码,而是忽略与断言相关的语句
不需要借助任何中间变量,且性能更好
一般情况下 Python 表达式的计算顺序是从左到右,但遇到表达式赋值的时候表达式右边的操作数先于左边的操作数计算
延迟/惰性 计算,真正需要执行的时候才计算表达式的值
优点:
避免不必要的计算,带来性能上的提升
对于 or 条件表达式应该将值为真可能性较高的变量写在 or 的前面,而 and 则应该推后。
节省空间,使得无限循环的数据结构成为可能
生成器和迭代器,yield
作为动态性的强类型脚本语言,python中的变量在定义的时候并不会指明具体类型
python解释器 会在运行的时候自动进行类型检查并根据需要进行隐式类型转换,如果变量类型不同又不能进行隐式类型转换则抛出TypeError的异常
基于内建类型扩展的用户自定义类型,type函数不能准确返回结果
古典类中,所有类的实例的type值都相等
如果类型有对应的工厂函数,可使用工厂函数对类型做相应转换,否则使用isinstance函数
主要是python2中存在问题
浮点数可能是不准确的,浮点数的比较同样最好能够指明精度
eval(expression[,globals[,locals]])
globals为字典形式,local为任何映射对象,标是全局和局部命名空间
实际应用过程中如果使用对象不是信任源,应该避免使用eval,可以替换成安全性更好的ast.literal_eval替代
lazy
实现方法:
要获取迭代过程中字典的 key 和 value,应该使用 iteritems
方法。
id()函数可查看变量在内存中的具体存储空间
is 对象标示符号 object identity 比较两个对象在内存中是否拥有同一块内存空间
== 是否相等 equal
==操作符可以被重载,is不能被重载
x is y为True x==y也为True 特殊情况除外 a = float("Nan") a==a
优点:
合理组织代码,便于维护和使用
能有效避免名称空间冲突,名称一样时,模块前缀不同则可区分
import 包名
import 包名.模块名
import 语句
from ... import ...
注意:
一般情况下尽量优先使用 import a
有节制地使用from a import b
尽量避免使用from a import *,会污染命名空间
**无节制使用from a import **
命名空间冲突
使用场景:
只需要导入部分属性或方法
模块中属性和方法访问频率较高导致使用模块名.包名进行访问过于繁琐
明确说明需要使用 from A import B,或者此形式导入更为简单和便利
循环嵌套导入问题,直接使用import
++i在python中语法合法,但不是自增操作,而是+(+i),即加上一个正数i
文件操作完成后应当立即关闭,否则不仅占用系统资源,还有可能影响其他程序或进程的操作
with expr1 as e1: with expr2 as e2:
在文件处理时使用 with 的好处在于无论程序以何种方式跳出 with 块,总能保证文件被正确关闭
任何实现了上下文协议的对象都可以称为一个上下文管理器
for和while语句中,else在循环正常结束和循环条件不成立时被执行
try:
except:
else:
finally:
常量None 空值对象
数据类型为NoneType,遵循单例模式,唯一,无法创建None对象
所有赋值为None的变量都相等
None与其他任何非None的对象(包括0,"")比较结果都为False
__nonzero__()
方法:该内部方法用于对自身对象进行空值测试,返回 0/1
或 True/False
。如果一个对象没有定义该方法,Python 将获取 __len__()
方法调用的结果来进行判断。__len__()
返回值为 0 则表示为空。如果一个类中既没有定义 __len__()
方法也没有定义 __nonzero__()
方法,该类的实例用 if
判断的结果都为 True
。
python中字符串为不可变对象
join()方法效率高于 “+”操作符
join方法连接字符串会首先计算所需申请总内存空间,然后一次性申请所需内存,将字符序列中的每一个元素复制到内存中,join操作的时间复杂度为O(n)
执行一次 +
操作便会在内存中申请新的内存空间,
并将上一次操作的结果和本次操作的右操作数复制到新申请的内存空间
在 N
个字符串连接的过程中,会产生 N-1
个中间结果,总共需要申请 并复制N-1
次内存,从而严重影响了执行效率,时间复杂度近似为 O(n^2)
。
int str tuple 不可变对象
dict list set 可变对象
区分标准:其值能否被修改
6切片操作相当于浅拷贝
列表推导式 list comprehension
[expr for iter_item in iterable if cond_expr]
优点:
1.直观清晰,代码简洁
2.解析效率更高(对于大数据处理,列表解析并不是一个最佳选择,过多的内存消耗可能会导致 MemoryError
)
传对象或者说是传对象的引用
函数参数在传递的过程中将整个对象传入,
对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象。
而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的。
不想让默认参数所指向的对象在所有的函数调用中被共享,而是在函数调用的过程中动态生成,可以在定义的时候用 None 对象作为占位符。
*args 可变参数列表,接受一个包装为元组形式的参数列表来传递非关键字参数,参数个数可以任意。
**kwargs, 接受字典形式的关键字参数列表,其中字典的键值对分别表示不可变参数的参数名和值。
原因:
适用场景:
为函数添加装饰器
参数数目不确定
实现函数的多态或者在继承的情况下子类需要调用父类的方法
类名.方法名 实例.方法名
类方法在调用的时候没有显式声明cls,类本身作为隐藏参数传入
Python中字符串有str和unicode两种,python3已简化
isinstance(s, basestring)
sorted(iterable,cmp,key,reverse)
s.sort(cmp,key,reverse)
区别:
sorted()作用于任何可迭代对象,sort()一般作用于列表
sorted()会返回一个新的排序列表,sort()会直接作用于原列表,返回None,消耗内存较少,效率较高
传入key比传入参数cmp效率要高,cmp传入函数在整个排序过程中会调用多次,key仅做一次处理
赋值不是给容器装数据,而是给数据贴标签。因此
变量A = 变量B,值和地址都相等
浅拷贝是把存放变量的地址的值传给新变量,引用同一地址
深拷贝是开辟了新的内存地址存放要赋值的变量的值
argparse
处理命令行参数ElementTree
解析 XMLlxml解析XML文档
elementree 的 iterparse 工具能够避免将整个 XML 文件加载到内存,从而解决当读入文件过大内存而消耗过多的问题
序列化:把内存中的数据结构在不丢失其身份和类型信息的情况下转换成对象的文本或二进制表示的过程
cPickle相对pickle性能更好,速度更快,1000倍。
dump()
和 load()
便可轻易实现序列化和反序列化。限制:
traceback.print_exc()
错误类型
错误对应的值
具体的trace信息,包括文件名,具体行号,函数名以及对应源代码
inspect 模块也提供了获取 traceback 对象的接口
inspect.trace()
inspect.stack()
函数查看函数层级调用的栈相关信息
等级
DEBUG
INFO
WANRING
ERROR
CRITICAL
主要对象:
logger是程序信息输出的接口,分散在代码中,根据设置的日志级别或filter来决定哪些信息需要输出,并分发到关联的handler()
Handler 处理信息输出,将信息输出到控制台、文件或者网络
StreamHandler发送错误信息到流,FileHandler用于向文件输出日志信息
Formatter:决定 log 信息的格式
Filter:用来决定哪些信息需要输出。可以被 handler 和 logger 使用,支持层次关系
logging 支持 logging.config 进行配置,支持 dictConfig 和 fileConfig 两种形式,其中 fileConfig 是基于 configparser()
函数进行解析,必须包含的内容为 [loggers]
、[handlers]
和 [formatters]
。
注意
1.尽量为logging取一个名字而不是采用默认
2.logging的名字建议以模块或者class命名
3.Logging是线程安全的,不支持多进程写入同一日志文件
GIL线程锁使得Python多线程编程暂时无法充分利用多处理器的优势
thread 模块提供了多线程底层支持模块,以低级原始的方式来处理和控制线程,使用起来较为复杂;
而 threading 模块基于 thread 进行包装,将线程的操作对象化
创建方式:
继承 Thread 类,重写它的 run() 方法(注意不是 start() 方法)
创建一个 thread.Thread 对象,在它的初始化函数(__init__()
)中将可调用对象作为参数传入
threading模块
使用threading的优点
1。threading的支持更完善和丰富
thread模块只提供一种锁类型
threading不仅有Lock指令锁
RLock可重入指令锁,条件变量condition
信号量Semaphore BoundedSemaphor 以及 event事件
2.threading.join()可阻塞当前上下文环境的线程,可便利地控制主线程和子线程之间的执行
3.thread不支持守护线程。主线程退出时不会提示,所有子线程会被强制结束
Queue.Queue(maxsize) maxsize>0时为无线循环队列
Queue.LifoQueue(maxsize):后进先出相当于栈
Queue.PriorityQueue(maxsize):优先级队列
Queue 模块中的队列和 collections.deque
所表示的队列并不一样,前者主要用于不同线程之间的通信,它内部实现了线程的锁机制;而后者主要是数据结构上的概念,因此支持 in 方法。
作用:
保证系统中一个类只有一个实例并且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源
发布订阅模式 publish/subscribe
发布者和订阅者不需要知道对方的存在,需要终极那代理人broker
通过在不同的条件下将实例的方法(即行为)替换掉,就实现了状态模式。但仍然有缺陷:
Python中一切皆对象
python2.2版本前,类和类型(type)并不统一
type()
的值和 __class__
的值是一样的,但古典类中实例的 type
为 instance
,其 type()
的值和 __class__
的值不一样instance
types.ClassType
,新式类的元类为 type
类a = Class(args)
__new__()方法才会真正创建实例,是类的构造方法
__init__()方法所做的工作是在类的对象创建好后进行变量的初始化
__new__是静态方法,init是实例方法
__new__()
方法,而控制实例初始化的时候使用 __init__()
方法__new__()
方法,但当子类继承自不可变类型,如 str
、int
、unicode
或者 tuple
的时候,往往需要覆盖该方法__new__()
和 __init__()
方法的时候这两个方法的参数必须保持一致,如果不一致将导致异常__init__()
能满足大部分需求,特殊情况下需要覆盖 __new__()
方法locals()查看局部变量
globals() 查看全局变量
变量名所在的命名空间直接决定了其能访问到的范围
变量解析机制遵循LEGB法则
局部作用域 > 嵌套作用域 > 全局作用域 >内置作用域
局部作用域:函数内外变量名互不冲突
全局作用域:定义在python模块文件中的变量名
嵌套作用域:如果想在嵌套的函数内修改外层函数中定义的变量,即使使用 global 进行申明也不能达到目的,其结果最终是在嵌套的函数所在的命名空间中创建了一个新的变量。
内置作用域:标准库中名为__builtin__的模块实现的
nonlocal关键字用来在函数或其他作用于中使用外层变量
编程语言不提倡全局变量,且这种写法影响业务逻辑
self表示实例对象本身,即类的对象在内存中的地址
在方法声明的时候需要定义 self 作为第一个参数,而调用方法的时候却不用传入这个参数
Python哲学是,显式优于隐式
古典类:MRO 深度优先,按照多继承申明的顺序形成继承树结构,自顶向下采用深度优先的搜索顺序
新式 C3MRO 广度优先
菱形继承是在多继承设计的时候尽量避免的问题
__dict__类属性,包含了所有属性
实例属性查找,找不到则进类属性找
能否给类增加属性
能,动态的增减对象的属性与方法是Python动态语言的特性
不能,内置类型和用户定义的类型是有区别的,内置类型不能随意增加属性或方法
通过实例访问 obj.x __get__(obj,type(obj))
通过类访问 cls.x __get__(None,type(obj))
__getattr__()
和 __getattribute__()
方法__getattr__()
和 __getattribute__()
都可以用作实例属性的获取和拦截(仅对实例属性(instance varibale)有效,非类属性)
__getattr__()适用于未定义的属性,__getattribute__()适用于所有属性的访问
property 实际上是实现了__get__() __set__()方法的类
数据描述符:如果一个对象同时定义了 __get__()
和 __set__()
方法,则称为数据描述符,如果仅定义了 __get__()
方法,则称为非数据描述符
优点:
代码更简洁,可读性更强
更好的管理属性的访问。设置校验、检查赋值的范围以及对某个属性进行二次计算之后再返回给用户或者计算某个依赖于其他属性的属性。
可维护性
控制属性访问权限,提高数据安全性
内置有__iter__方法的对象,都称为可迭代对象,可迭代的对象:str,list,tuple,dict,set,文件对象
iter()函数返回一个迭代器对象,接受的参数实现了__iter__()方法的容器或迭代器
迭代器一定是可迭代对象,但可迭代对象不一定是迭代器
迭代器优缺点
1.不依赖索引的迭代取值方式
2.惰性计算,同一时刻在内存中只存在一个值,更节省内存
1.取值方式不够灵活,不能取指定的值
2.无法预测迭代器的长度
生成器:按一定算法生成一个序列
迭代器不是生成器,生成器可以在一定程度上看做迭代器
使用yield语句的函数就是生成器函数
当第一次调用 next()
方法时,生成器函数开始执行,执行到 yield 表达式为止。
协程,又称微线程和纤程等
大部分协程的实现是协作式而非抢占式的,需要用户自己去调度,所以通常无法利用多核,但用来执行协作式多任务非常合适
GIL 被称为全局解释器锁(Global Interpreter Lock)
它的作用是保证任何情况下虚拟机中只会有一个线程被运行,而其他线程都处于等待 GIL 锁被释放的状态。不管是在单核系统还是多核系统中,始终只有一个获得了 GIL 锁的线程在运行,每次遇到 I/O 操作便会进行 GIL 锁的释放。
引用计数
即针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。引用该对象时,计数+1,否则-1.
缺点:无法解决循环引用的问题
标记清除
第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收
缺点:
清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
分代回收
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
单元测试用来验证程序单元的正确性,一般由开发人员完成
创建测试计划
编写测试用例,准备测试数据
编写测试脚本
编写被测代码,在代码完成之后执行测试脚本
修正代码缺陷,重新测试直到代码可接受为止
基本原则:
一致性
原子性
单一职责
隔离性 独立的,无条件逻辑依赖
unittest
1.优先保证代码可工作
2.权衡优化的代价,牺牲时间换空间或者空间换时间
3.定义性能指标,集中力量解决首要问题
4.可读性
memory_profiler
和 objgraph
剖析内存使用O(1) < O(log * n) < O(n) < O(n log n) < O(n^2) < O(c^n) < O(n!) < O(n^n)
算法复杂度分析建立在同一语言实现的基础上
减少循环内部的计算
将显式循环改为隐式循环
循环中尽量引用局部变量,局部变量比全局变量的查询快
yield语句与return语句相似,解释器执行遇到yield的时候,函数会自动返回yield语句之后的表达式的值
yield 语句在返回的同时会保存所有的局部变量以及现场信息,以便在迭代器调用 next()
或 send()
方法的时候还原,而不是直接交给垃圾回收器(return()
方法返回后这些信息会被垃圾回收器处理
优点:
用户一般不需要自己实现__iter__和next方法,默认返回迭代器
简洁优雅
惰性计算,节省内存空间,提高效率
协同程序更易实现
list:list对象如果经常有元素数量的巨变,应当考虑使用deque
deque是双端队列,同时具备栈和队列的特性
集合 set是通过Hash算法实现的无序不重复的元素集
涉及list求交集、并集或者差等问题可以转换为set操作
多进程管理包
每个进程空间地址独立,进程间的数据空间也相互独立,数据共享和传递不如线程方便