Effective Python读书笔记
有些位置可能翻译理解的不到位,各位看官如有疑问,欢迎留言赐教。
Pythonic Thinking
大家经常用Pythonic来形容python语法风格的编程方式:简单优美,没有之一;通过import this
查看Python之禅。
使用python之前需要明确使用的是python2 or python3。推荐使用python3。
PEP8编码风格指南
- PEP8(Python Enhancement Proposal #8 ),点击查看官方文档,按这种方式编码你的Python更加优美。
缩进留白Whitespace
-
python通过强制缩进的方式,控制字代码块;每次缩进使用4个空格符;
-
单行代码不宜超过79个字符,当使用
\
分行显示时,换行的部分应该缩进4个空格位。 -
文件中,函数和类之间空2行;类中,方法之间空1行;
-
列表索引访问、函数调用、关键字参数赋值时不应该使用空格;变量赋值间应该空一格;
命名
-
函数名、变量名、类的属性名采用:小写字母加下划线的方式;
-
保护对象属性命名采用:下划线开头加小写字母加下划线的方式;
-
私有对象属性命名采用:双下划线开头加小写字母加下划线的方式;
-
类名、异常命名采用:驼峰体的方式;
-
常量命名采用:全大写字母的方式;
-
类中对象自己使用
self
, 类自己使用cls
表达式&声明
-
判断对象是否不相等使用
if a is not b
而不使用if not a is b
-
判断对象是否为空使用
if not a
, 而不使用if len(a) == 0
-
判断对象不为空使用
if a
-
当子代码块只有一行时,不要在写在一块
if True: print('hello world') # 不推荐,建议换行
- 导入模块时,在文件顶部导入;导入时应该使用完成的导入路径
from bar import foo
import foo # 不推荐
- 导入模块的顺序:标准库,三方库,自己的库;在每一等级内都按字母顺序导入
区别:bytes & str & unicode
-
Python3中,bytes指的是包含8-bit的二进制数据;str指的是Unicode字符。bytes和str是两个完全独立的对象;
-
Python2中,str指的是包含8-bit的二进制数据;unicode指的是Unicode字符;str和unicode处理7-bit的ASCII时是相同的。
# Python3中,定义函数将bytes or str转换成str
def to_str(bytes_or_str):
if isinstance(bytes_or_str, bytes):
value = bytes_or_str.decode(‘utf-8’)
else:
value = bytes_or_str
return value # Instance of str
# Python3中,定义函数将bytes or str转换成bytes
def to_bytes(bytes_or_str):
if isinstance(bytes_or_str, str):
value = bytes_or_str.encode(‘utf-8’)
else:
value = bytes_or_str
return value # Instance of bytes
- Python3中,文件操作时,文本文件用
t
模式,二进制文件用b
模式;
函数优于复杂的表达式
切片Slice
a = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’]
# a[start:end]
a[:] # [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’]
a[:5] # [‘a’, ‘b’, ‘c’, ‘d’, ‘e’]
a[:-1] # [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’]
a[4:] # [‘e’, ‘f’, ‘g’, ‘h’]
a[-3:] # [‘f’, ‘g’, ‘h’]
a[2:5] # [‘c’, ‘d’, ‘e’]
a[2:-1] # [‘c’, ‘d’, ‘e’, ‘f’, ‘g’]
a[-3:-1] # [‘f’, ‘g’]
-
切片的使用遵循 '顾头不顾尾'的基本原则;
-
当
start
、end
不指定表示从头切或切到尾;都不指定则表示全部拷贝一份,切片属于浅拷贝; -
使用切片不会出现索引越界的问题,如
a[:20]
表示切前20个元素; -
start或end为负数,表示倒数第几个,同样遵循 '顾头不顾尾'的基本原则;
-
如果想要从头切不要提供start,切到尾时不要提供end,如a[:5],切前5个
-
切片用于赋值时,表示切片的那些位置用新的值覆盖
a[2:7] = [99, 22, 14] # a = [‘a’, ‘b’, 99, 22, 14, ‘h’]
注意:列表切片赋值修改的是列表内元素的绑定关系,列表对象的内存地址没变。(旧瓶子换了新酒)
b = a
a[:] = [101, 102, 103]
print (a is b) # True
-
切片时尽可能避免同时使用三个参数:
a[start:end:step]
,这样很很容易让人头晕(我也是醉了)a[2:5:-2]
-
step表示间隔几个切一片,是步长的意思。步长为负数表示倒着切;最好不好使用倒着切;
列表生成式 List Comprehensions
a = [x for x in range(10)]
aquares = [x**2 for x in a]
- 尽量使用列表生成式而不是用map()和filter()内置函数。因为map()和filter()一般要配合匿名函数的使用,不简洁。
squares = map(lambda x: x ** 2, a)
even_squares = [x**2 for x in a if x % 2 == 0]
alt = map(lambda x: x**2, filter(lambda x: x % 2 == 0, a)) # 表达式很繁琐
assert even_squares == list(alt)
-
字典和集合等也有对应的生成式。
-
列表生成式可以多层嵌套,可以后接判断条件;当列表生成式出现超过两层嵌套或者多个条件判断时,不建议使用,建议改写成包含
if
和for
的函数。 -
数据量过大时,使用表达式生成器(generator expressions )替换列表生成式的使用,减少内存消耗。
-
多使用
enumerate
少使用range
for i in range(len(flavor_list)): # 需要获取列表长度
flavor = flavor_list[i] # 通过索引取值
print(‘%d: %s’ % (i + 1, flavor))
for i, flavor in enumerate(flavor_list, 1):
print(‘%d: %s’ % (i, flavor))
-
同时并行循环两个列表使用
zip
-
避免在
while
或者for
循环体外后接else
代码块(pep8规范) -
充分捕获异常:try|except|else|finally
函数
函数返回值不推荐使用None
- 函数返回值有具体意义时,不推荐返回None;因为调用函数的对象使用判断时无法区分出
None、0、[]
;应该采用在函数体中捕获异常替代None。
闭包函数只读外部函数作用域变量
- 闭包函数可以引用它定义时外部函数作用域的变量a,但不可以修改赋值修改该变量,因为修改的操作会在闭包函数的局部作用域内增加一个新的变量而不是修改器外部函数作用域内的变量。
- 但非要将在闭包寒素内修改外部作用域的变量也可以,但不推荐(容易出难以排查的bug)。
- python3中通过
nonlocal
语法声明接下来要操作的边外部函数作用域的a;python2没有nonlocal
语法,是通过将变量定义成可变数据类型,比如列表,在闭包函数里通过a[0]
修改。
推荐使用生成器而不是直接返回一个列表
- 当数据序列数据量庞大时,使用列表粉肠消耗内存,而生成器只是保存序列的算法,每次调用时返回一个。
使用不定长度的位置参数*args
优化函数传参调用时的简洁性
- 函数定义时使用
*args
可以接收多个位置参数,args以元组的形式保存传进来的多个数据; - 函数调用时可以通过
*operator
解压可迭代对象operator中的序列数据,按位置参数赋值给形参; - 调用函数传实参时
*operator
,operator避免使用生成器否则可能会消耗大量内存导致程序崩溃; - 使用
*args
的缺点是:函数未来增加新位置行参时很容易出bug且很难排查。
建议使用关键字参数传参给形参
- 关键字传参可以不按照位置顺序,只要每个形参都有被赋值即可;可以同时使用位置传参和关键字传参,但关键字传参要放在位置传参后面,且每个形参只能被赋值一次;
- 关键字传参好处一:每个参数表示的意义清晰明了;第二个好处:导致出现了函数定义时的默认形参;
- 第三个好处:向下兼容性,更新函数功能后(新增了默认参数)不会影响老用户使用该函数的调用接口;老用户使用默认的值,新用户可以使用默认参数指定新的值;
def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1):
return ((weight_diff / units_per_kg) / time_diff) * period
# 老用户使用时不知道函数定义代码已经修改了,依然使用可以旧的两个参数传参
# 而新用户知道新增的两个参数表示意思,需要时指定自定义值即可
pounds_per_hour = flow_rate(weight_diff, time_diff, period=3600, units_per_kg=2.2)
- 这种向下兼容性对于使用*args接收参数的函数很重要;
- 默认参数最好使用关键字传参而不用位置参数传参。
使用None和文档注释的方式为函数参动态的参数
- 默认参数只会在函数定义时赋值一次,如果默认参数等于{}或[],这将导致后面所有的函数调用者访问的都是同一个字典或列表的内存地址;
- 使用默认值为None,再加文档注释的方式实现动态参默认参数
# 记录消息的打印时间
def log(message, when=datetime.now()):
print(‘%s: %s’ % (when, message))
log(‘Hi there!’)
sleep(0.1)
log(‘Hi again!’)
>>>
2014-11-15 21:10:10.371432: Hi there!
2014-11-15 21:10:10.371432: Hi again! # 两次的时间是相同的
# 这是因为when这个默认参数在函数定义的瞬间就被赋值了,以后每次调用使用的都是一个相同的值;
# 如果想要时时获得打印消息的时间,使用默认参数为None的方式
def log(message, when=None):
"""
Log a message with a timestamp.
Args:
message: Message to print.
when: datetime of when the message occurred.
Defaults to the present time.
"""
when = datetime.now() if when is None else when
print(‘%s: %s’ % (when, message))
限制函数调用者使用关键字参数为函数传参
- python3中函数定时,参数列表中添加
*
,其后的参数必须使用关键字传参,否则会报TypeError 的错误; - python2中没有该语法,若非要这样做可以使用**kwargs加手动抛出TypeError的错误。
update to 20200323
大佬总结的很好,直接引用学习了