《流畅的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,对形参对象的修改等于对实参修改。
可变类型参数
不要用可变类型作参数默认值
函数默认值在定义函数时求解(加载模块时),因此默认值变成了函数对象的属性,因此所有用到此默认值的函数都在用同一个对象,其中一个改了其他的函数也跟着改。from typing import List
class Bad:
def __init__(self, l = []):
self.l = l
class AllRight:
def __init__(self, l:List[Any] = None):
if (l is not None):
self.l = l
else:
self.l = []
传入可变参数时小心直接修改
|
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对象。café = bytes('café', encoding ='utf8')
print(café) # bytes对象b'caf\xc3\xa9'
print(café[:1]) # bytes对象b'c'
print(café[0]) # 整数99
bytes对象中的部分ASCII字符显示ASCII字符本身,制表符、换行符等显示成转义序列,其他使用十六进制转义序列显示。
编码
UnicodeEncodeError
encode的参数errors指定编码遇到无法处理的字节的时候的行为,ignore跳过,replace替换为?,xmlcharrefreplace替换为对应XML实体,这是唯一不丢失信息的处理方法。
UnicodeDecodeError
使用错误的编码方式解码字节可能会得到乱码的字符串,此时需要chardet包判断字节编码
BOM
BOM是字节序标记(byte-order mark),指明编码时使用Intel CPU的小端序。café2 = bytes('café', encoding ='utf16')
print(café2) # b'\xff\xfec\x00a\x00f\x00\xe9\x00'
list(café2) # [255, 254, 99, 0, 97, 0, 102, 0, 233, 0]
\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
。但他们都有现代替代品:# 0 ~ 5 阶乘列表
bad1 = list(map(factorial, range(6)))
good1 = [factorial(n) for n in range(6)]
# 0 ~ 5 中的奇数的阶乘列表
bad2 = list(map(factorial, filter(lambda n : n % 2, range(6))))
good2 = [factorial(n) for n in range(6) if n % 2]
reduce
在12.7节
位置参数、关键词参数
|
在tag
中,class_
只能作为关键字参数传入,content
可以捕获任意数量的参数作为一个元组,attrs
可以捕获任意数量的关键字参数作为一个字典。
仅限关键字参数
*
后的参数只能作为关键字参数传入,如tag()
中*content
后的class_
。如果不想*content
捕获任意数量的参数,可以用*
替代。def f(a, *, b):
return a, b
这里要想传入b只能用f(1, b=2)
的方式
仅限位置参数
Python 3.8开始可以使用/
定义仅限位置参数的函数:def divmod(a, b, /):
return (a // b, a % b)
这样就只能用divmod(10, 3)
调用,不能写成divmod(a=10, b=3)
。
函数式编程
operator
模块
operator
模块含有对象形式的运算符,可以把运算符转换成函数:from functools import reduce
def factorial(n):
return reduce(lambda a, b: a*b, range(1, n+1))
from operator import mul
def factorial2(n):
return reduce(mul, range(1, n+1))
类似的,获取序列中的元素的[]
运算符有operator.itemgetter()
,获取对象属性的.
有operator.attrgetter()
,调用对象方法的.
有operator.methodcaller()
等。from operator import methodcaller
str = 'foo bar'
up = methodcaller('upper')
up(str) == str.upper() # True
hyphenate = methodcaller('replace', ' ', '-')
hyphenate(str) == str.replace(' ', '-') # True
冻结部分参数
如前所述,通过operator.methodcaller()
可以为函数指定默认参数,functools.partial
也可以做到。from operator import mul
from functools import partial
triple = partial(mul, 3)
list(map(triple, range(1, 10)))
可以用来把函数包装成更简单的API:picture = partial(tag, 'img', class_='pic-frame')
装饰器和闭包
装饰器
装饰器是一种语法糖:def deco(func):
print(f'running deco({func})')
return func
@deco
def f(): # output: running deco(<function f at 0x000001CE2C0BEAF0>)
print(f'running f')
f() # output: running f
相当于f = deco(f)
f()
装饰器在被装饰的函数定义后执行,一般是模块被导入时。
变量作用域
|
python不要求声明变量,但会假定在函数主体中赋值的变量是局部变量。想要在函数内引用全局变量的话需要global
声明。b = 6
def f(a):
global b
print(a) # 1
print(b) # 6
b = 4
print(b) # 4
f(1)
print(b) # 4
闭包
|
如果想节省内存空间,只保留total和num的话def make_averager():
total = 0
num = 0
def averager(new_value):
num += 1 # UnboundLocalError: local variable 'num' referenced before assignment
total += new_value
return total / count
return averager
因为num += 1
已经算赋值语句了,num
会被视为局部变量,需要用到nonlocal num, total
,将num, total
从外层函数拿进当前作用域,作为自由变量导入averager
的闭包中。
改版之前由于series
是可变对象,没有进行赋值,所以一直是自由变量,自动导入了averager
的闭包中,不会出现问题。
常用装饰器
cache
singledispatch
设计模式
符合设计模式并不表示做得对。
——Ralph Johnson
虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用。活用语言特性可以