《流畅的Python 第一版》非扫描版PDF电子书网址:https://github.com/silenove/python_ebook/blob/master/%E6%B5%81%E7%95%85%E7%9A%84python.pdf
(第二版已经出版)
对象引用
变量就像一个标签,a = obj
就像把a
这个标签贴到obj
上了。
== 与 is
a == b
用于比较两引用的对象(Java中的.equal()
),a is b
用于比较两引用 是否指向同一对象(id(a) == id(b)
)(Java中的==
)
简单区分:
is
只用于a is None
a is not None
,剩下都用==
浅拷贝
|
l2
l3
都是l1
的浅拷贝,l1
中的引用(如列表中的列表)都被直接复制到浅拷贝中,因此其中一个改了其他的也跟着改。
|
l4
进行深拷贝时循环将其中所有对象引用复制一份。
共享传参
Python函数传参只支持共享传参call by sharing方式,亦即传引用call by reference,对形参对象的修改等于对实参修改。
可变类型参数
不要用可变类型作参数默认值
函数默认值在定义函数时求解(加载模块时),因此默认值变成了函数对象的属性,因此所有用到此默认值的函数都在用同一个对象,其中一个改了其他的函数也跟着改。
|
传入可变参数时小心直接修改
|
del
del
是语句,虽然del x
等价于del(x)
,因为Python中x
等价于(x)
。
del
删除的是引用,当对象最后一个引用也被删除(或被重新绑定到其他对象)时才销毁对象回收内存。
字符串与字节
基本概念
基本概念 | 解释 | 举例 |
---|---|---|
字符character | 人类可读的Unicode字符(取值为U+0000~U+10FFFF的数) | ‘A’ (U+0041) |
字符串string | 人类可读的字符序列 | ‘AB’ (U+0041 U+0042) |
字节序列bytes | 对字符串使用某种方式编码后得到的整数序列 | b’\x41\x21’(UTF-8) |
string —编码-> bytes
string <-解码— bytes
字节
对str来说,str[0] == str[:1],但对于其他任何序列[0]和[:1]都不一样。bytes对象的元素是一个range(256)的整数,bytes的切片还是bytes对象。
|
bytes对象中的部分ASCII字符显示ASCII字符本身,制表符、换行符等显示成转义序列,其他使用十六进制转义序列显示。
编码
UnicodeEncodeError
encode的参数errors指定编码遇到无法处理的字节的时候的行为,ignore跳过,replace替换为?,xmlcharrefreplace替换为对应XML实体,这是唯一不丢失信息的处理方法。
UnicodeDecodeError
使用错误的编码方式解码字节可能会得到乱码的字符串,此时需要chardet包判断字节编码
BOM
BOM是字节序标记(byte-order mark),指明编码时使用Intel CPU的小端序。
|
\xff\xfe (255,254 或者说U+FFFE)就是BOM,因为Unicode中没有U+FEFF,所以只要有\xff\xfe就可以判断是小端序。
UTF-16有两个变种,可以显式指明使用小端序(utf-16le)或是大端序(utf-16be)
字节序只对多字节编码如UTF-16 UTF-32起作用,因此UTF-8不用添加BOM。但有的程序还是会添加BOM,带BOM的UTF-8编码在python中的解码器叫UTF-8-SIG,它的U+FEFF是一个三字节序列b’\xef\xbb\xbf’
最佳实践
- Unicode三明治原则,即输入时把字节序列解码成str文本,业务逻辑只处理str,输出时编码成字节序列
- 在open中始终明确encoding参数,特别是在Windows系统中
- 除非想用chardet判断编码或打开非纯文本文件,否则不要用’b’模式打开文件
- 在比较字符串前应使用unicodedata.normalize()规范化字符串,将字符+组合附加符号(两个码点)和自带符号的字符(一个码点)转换成相同的形式,并且用
- 字符串排序可以直接使用pyuca或PyICU库,在各个语言各个系统上都能正确工作
函数
高阶函数
接受函数为参数或返回函数的函数是高阶函数,如map
filter
reduce
。但他们都有现代替代品:
|
reduce
在12.7节
位置参数、关键词参数
|
在tag
中,class_
只能作为关键字参数传入,content
可以捕获任意数量的参数作为一个元组,attrs
可以捕获任意数量的关键字参数作为一个字典。
仅限关键字参数
*
后的参数只能作为关键字参数传入,如tag()
中*content
后的class_
。如果不想*content
捕获任意数量的参数,可以用*
替代。
|
这里要想传入b只能用
f(1, b=2)
的方式
仅限位置参数
Python 3.8开始可以使用/
定义仅限位置参数的函数:
|
这样就只能用
divmod(10, 3)
调用,不能写成divmod(a=10, b=3)
。
函数式编程
operator
模块
operator
模块含有对象形式的运算符,可以把运算符转换成函数:
|
类似的,获取序列中的元素的
[]
运算符有operator.itemgetter()
,获取对象属性的.
有operator.attrgetter()
,调用对象方法的.
有operator.methodcaller()
等。
|
冻结部分参数
如前所述,通过operator.methodcaller()
可以为函数指定默认参数,functools.partial
也可以做到。
|
可以用来把函数包装成更简单的API:
|
装饰器和闭包
装饰器
装饰器是一种语法糖:
|
相当于
|
装饰器在被装饰的函数定义后执行,一般是模块被导入时。
变量作用域
|
python不要求声明变量,但会假定在函数主体中赋值的变量是局部变量。想要在函数内引用全局变量的话需要global
声明。
|
闭包
|
如果想节省内存空间,只保留total和num的话
|
因为
num += 1
已经算赋值语句了,num
会被视为局部变量,需要用到nonlocal num, total
,将num, total
从外层函数拿进当前作用域,作为自由变量导入averager
的闭包中。
改版之前由于series
是可变对象,没有进行赋值,所以一直是自由变量,自动导入了averager
的闭包中,不会出现问题。
常用装饰器
cache
singledispatch
设计模式
符合设计模式并不表示做得对。
——Ralph Johnson
虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用。活用语言特性可以
v1.5.2