Effective Python 读书笔记
1 |
|
用 Python 的方式思考
bytes str 和 unicode
接受 str 或 bytes 返回 str 的方法
1 |
|
接受 str 或 bytes 返回 bytes 的方法
1 |
|
切片
如果从序列的开头获取切片,那就不要在 start 那里写上 0 ,而是应该把它留空,这样代码看起来会清爽一些.
1 |
|
如果切片一直要取到列表末尾,那就应该把 end 留空,因为即使写了,也是多余.
1 |
|
切割列表时,如果指定了 stride,那么代码可能会变得相当费解.我们呢不应该把 stride 与 start 和 end 写在一起.如果非要用,那就尽量采用正值.同时省略 start 和 end 索引.如果一定要配合 start 或 end 索引来使用 stride,请考虑步进式切片,把切割结果赋给某个变量,然后二次切片.
尽量用 enumerate
取代 range
1 |
|
用 zip()
遍历两个列表
1 |
|
如果输入的迭代器长度不同, 受封装的那些迭代器中,只要有一个耗尽了,zip
就不再产生新的元组了
函数
尽量用异常来表示特殊情况,而不要返回 None
1 |
|
类与继承
尽量用辅助类来维护程序的状态, 而不要用字典和元组
很容易就能用 Python 内置的字典与元组类型构建出分层的数据结构, 从而保存程序的内部状态. 但是, 当嵌套多于一层的时候, 就应该避免这种做法(不要使用包含字典的字典), 这种多层嵌套的代码, 其他人很难看懂, 而且自己维护起来也很麻烦.
用来保存程序状态的数据结构一旦变得过于复杂, 就应该将其拆解为类, 以便提供更为明确的接口, 也能够在接口与具体实现之间创建抽象层.
把嵌套结构重构为类
collections
模块着的 namedtuple
(具名元组)类型非常适合这种需求, 使用它很容易定义出精简而又不可变的数据类.
1 |
|
要点
- 不要使用包含其它字典的字典, 也不要使用过长的元组
- 如果容器中包含间的而又不可变的数据, 那么可以先使用
nametuple
来表示, 再修改为完整的类 - 保存内部状态的字典如果变得比较复杂, 那就应该把这些代码拆解为多个辅助类.
只在使用
Mix-in
组件制作工具类时进行多重继承待续
多用 public 属性, 少用 private 属性
元类及属性
用纯属性取代
get
和set
方法使用
@property
装饰器在设置属性的时候实现特殊行为.1
2
3
4
5class Resistor():
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0下面这个子类继承自 Resistor, 它在给 voltage(电压)属性赋值的时候,还会同时修改 current(电流)属性.
setter
和getter
方法的名称必须与相关属性相符.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class VoltageResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
self._voltage = 0
@property
def voltage(self):
return self._voltage
@voltage.setter
def voltage(self, voltage):
self._voltage = voltage
self.current = self._voltage / self.ohms
if __name__ == '__main__':
r2 = VoltageResistance(3)
print('Before: {} amps'.format(r2.current))
r2.voltage = 10
print('after: {} amps'.format(r2.curren>>>
1
2Before: 0 amps
after: 3.3333333333333335 amps用
@property
来代替属性重构带有配额的漏桶.
代码略漏桶算法是一种具备传输, 调度和统计等用途的算法. 它把容器比作底部有漏洞的桶(leakybucket), 把配额(quota)比作桶底漏出的水.
用描述符来改写需要复用的
@property
方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Grade():
def __get__(*args, **kwargs):
#...
def __set__(*args, **kwargs):
# ...
class Exam():
#class attributes
math_grade = Grade()
writing_grade = Grade()
science_grade = Grade()
if __name__ == '__main__':
exam = Exam()
exam.writing_grade = 40为属性赋值时, Python 会将其转译:
Exam.__dict__['writing_grade'].__set__(exam, 40)
在获取属性时print(exam.writing_grade)
Python 也会将其转译print(Exam.__dict__['writing_grade'].__get__(exam, Exam))
用
__getattr__ __getattribute__ 和 __setattr__
实现按需生成的属性__getattr__
1
2
3
4
5
6
7
8
9
10
11
12
13
14class LazyDB():
def __init__(self):
self.exists = 5
def __getattr__(self, name):
value = 'value for {}'.format(name)
setattr(self, name, value)
return value
if __name__ == '__main__':
data = LazyDB()
print('before: {}'.format(data.__dict__))
print('foo: {}'.format(data.foo))
print('after: {}'.format(data.__dict__))>>>
1
2
3before: {'exists': 5}
foo: value for foo
after: {'exists': 5, 'foo': 'value for foo'}然后给 LazyDB 添加记录功能, 把程序对
__getattr__
的调用行为记录下来. 为了避免无限递归, 需要在 LoggingLazyDB 子类里面通过super().__getattr__()
来获取真正的属性值.1
2
3
4
5
6
7
8
9
10
11class LoggingLazyDB(LazyDB):
def __getattr__(self, name):
print('Called __getattr__{}'.format(name))
return super().__getattr__(name)
if __name__ == '__main__':
data = LoggingLazyDB()
print('exists: {}'.format(data.exists))
print('foo: {}'.format(data.foo))
print('foo: {}'.format(data.foo))>>>
1
2
3
4exists: 5
Called __getattr__foo
foo: value for foo
foo: value for foo因为 exists 属性本身就在实例字典里面, 所以访问它的时候不会触发
__getattr__
. foo 属性初始时并不在实例字典里, 所以初次访问的时候会触发__getattr__
.__getattr__
调用setattr
方法, 把 foo 放在实例字典中, 所以第二次访问 foo 的时候不会触发__getattr__
.__getattribute__
程序每次访问对象的属性时, Python 会调用这个特殊方法, 即使属性字典里面已经有了该属性, 也依然会触发
__getattribute__
方法.
ValidatingDB 会在__getattribute__
方法里面记录每次调用的时间.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class ValidatingDB():
def __init__(self):
self.exists = 5
def __getattribute__(self, name):
print('Called __getattrbute__ {}'.format(name))
try:
return super().__getattribute__(name)
except AttributeError:
value = 'value for {}'.format(name)
setattr(self, name, value)
return value
if __name__ == '__main__':
data = ValidatingDB()
print('exists: {}'.format(data.exists))
print('foo: {}'.format(data.foo))
print('foo: {}'.format(data.foo))>>>
1
2
3
4
5
6Called __getattrbute__ exists
exists: 5
Called __getattrbute__ foo
foo: value for foo
Called __getattrbute__ foo
foo: value for foo按照 Python 处理缺失属性的标准流程, 如果程序动态地访问了一个不应该有的属性, 可以在
__getattr__
和__getattrbute__
里面抛出 AttributeError 异常.1
2
3
4
5
6
7
8class MissingPropertyDB():
def __getattr__(self, name):
if name == 'bad_name':
raise AttributeError('{} is missing'.format(name))
if __name__ == '__main__':
data = MissingPropertyDB()
data.bad_name>>>
1
2
3
4
5
6Traceback (most recent call last):
File "C:/Users/wter/OneDrive/pythonpj/half_a_wheel/half/test.py", line 54, in <module>
data.bad_name
File "C:/Users/wter/OneDrive/pythonpj/half_a_wheel/half/test.py", line 49, in __getattr__
raise AttributeError('{} is missing'.format(name))
AttributeError: bad_name is missing实现通用的功能时, 会在 Python 中使用内置的 hasattr 函数来判断对象是否已经拥有了相关的属性, 并用内置的 getattr 函数来获取属性值. 这些函数会在实例字典中搜索待查询的属性,然后再调用
__getattr__
.1
2
3
4
5data = LoggingLazyDB()
print('before: {}'.format(data.__dict__))
print('foo exists: {}'.format(hasattr(data, 'foo')))
print('after: {}'.format(data.__dict__))
print('foo exists: {}'.format(hasattr(data, 'foo')))>>>
1
2
3
4
5before: {'exists': 5}
Called __getattr__foo
foo exists: True
after: {'exists': 5, 'foo': 'value for foo'}
foo exists: True用元类验证子类
内容赞略
用元类来注册子类
内容赞略
用元类来注解类的属性
内容赞略
并发和并行
用
subprocess
模块来管理子进程1
2
3
4
5
6
7
8
9
10
11
12
13def run_sleep(period):
proc = subprocess.Popen(['sleep', str(period)])
return proc
start = time.time()
procs = []
for _ in range(0, 20):
proc = run_sleep(0.1)
procs.append(proc)
for proc in procs:
proc.communicate()
end = time.time()
print('Finished in {} s'.format(end - start))用协程来并发的运行多个函数
暂略
内置模块
用
function.wraps
定义函数修饰器用
datetima
模块来处理本地时间time 模块
datetime 模块
使用内置算法与数据结构
双向队列
collection
模块中的deque
类, 是一种双向队列. 从头部或者尾部插入或移除一个元素, 之需要消耗常数级别的时间.非常适合用来表示先进先出的队列.1
2
3fifo = deque()
fifo.append(1)
x = fifo.popleft()list 从尾部插入或者移除元素, 需要O(1), 但是从头部插入或移除元素会消耗线性级别的时间.
有序字典
collection
模块中的OrderedDict
类, 能够按照键的插入顺序, 来保留键值对在字典中的次序.带有默认值的字典
1
2stats = defaultdict(int)
stats[my_counter'] += 1堆队列
heapd
模块提供了heappush
heappop
和nsmallest
等函数, 能在标准的list类型中创建堆结构.二分查找
list 使用 index 方法来搜索某个元素, 所耗的时间会与列表的长度呈线性比例.
bisect
模块中的bisect_left
等函数, 提供了高效的二分析半搜索算法, 可以在一系列排好顺序的元素之中搜寻某个值.迭代器有关的工具
在重视精确度的场景, 应该使用
decimal
协作开发
文档
测试
Tips
使用大写的变量名称表示常量
习惯用下划线表示无用的变量