前言:这正是本书的主要目的:着重讲解这门语言的基本惯用法,让你的代码简洁、高效且可读,把你打造成熟练的 Python 程序员。
自己总结学习作为输出,很多为了节省时间只是复制粘贴,不具有广泛意义
1.通过实现特殊方法,展示python解释器是如何调用特殊方法;
1.Python 数据模型计算机编程语言中对象的属性;
2.元对象所指的是那些对建构语言本身来讲很重要的对象,以此为前提,协议也可以看作接口。也就是说,元对象协议是对象模型的同义词,它们的意思都是构建核心语言的 API
3.len 是特殊方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理。(解释:因为如果 x 是一个内置类型的实例,len(x)的背后会用 CPython 直接从 C 结构体中读取对象的长度,不调用任何方法,以至于 len(x) 会非常快。)
这种处理方式在保持内置类型的效率和保证语言的一致性之间找到了一个平衡点,也印证了“Python 之禅”中的另外一句话:”不能让特例特殊到开始破坏既定规则。”
Alex Martelli 的《Python 技术手册(第 2 版)》对数据模型的讲解很精彩
David Beazley 著有两本基于 Python 3 的书,其中对数据模型进行了详尽的介绍。一本是《Python 参考手册(第 4 版)》 ,另一本是与 Brian K.Jones 合著的《Python Cookbook(第 3 版)中文版》
Python 文档里总是用“Python 数据模型”这种说法,而大多数作者提到这个概念的时候会说“Python 对象模型”。Alex Martelli 的《Python技术手册(第 2 版)》和 David Beazley 的《Python 参考手册(第 4版)》是这个领域中最好的两本书。
目的:深入理解 Python 中的不同序列类型,不但能让我们避免重新发明轮子,它们的 API 还能帮助我们把自己定义的 API 设计得跟原生的序列一样,或者是跟未来可能出现的序列类型保持兼容。
列表推导是构建列表(list)的快捷方式,而生成器表达式则可以用来创建其他任何类型的序列,写出可读性更好且更高效的代码的机会
>>> symbols = '$¢£¥€¤'
>>> codes = [ord(symbol) for symbol in symbols]
列表推导式使用场景
列表推导也可能被滥用。以前看到过有的 Python 代码用列表推导来重复获取一个函数的副作用。通常的原则是,只用列表推导来创建新的列表,并且尽量保持简短。如果列表推导的代码超过了两行,你可能就要考虑是不是得用 for 循环重写了。就跟写文章一样,并没有什么硬性的规则,这个度得你自己把握
句法提示
Python 会忽略代码里 []、{} 和 () 中的换行,因此如果你的代码里有多行的列表、列表推导、生成器表达式、字典这一类的,可以省略不太好看的续行符 \
>>> symbols = '$¢£¥€¤'
>>> beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
>>> beyond_ascii
[162, 163, 165, 8364, 164]
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes]
虽然也可以用列表推导来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。这是因为生成器表达式背后遵守了迭代器协议,可以逐个地产出元素,而不是先建立一个完整的列表,然后再把这个列表传递到某个构造函数里。前面那种方式显然能够节省内存
>>> symbols = '$¢£¥€¤'
>>> tuple(ord(symbol) for symbol in symbols)
(36, 162, 163, 165, 8364, 164)
>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols))
array('I', [36, 162, 163, 165, 8364, 164])
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
print(tshirt)
除了用作不可变的列表,它还可以用于没有字段名的记录,作为记录的功用
>>> lax_coordinates = (33.9425, -118.408056) ➊
>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) ➋
>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ➌
... ('ESP', 'XDA205856')]
>>> for passport in sorted(traveler_ids): ➍
... print('%s/%s' % passport) # ❺ % 格式运算符能被匹配到对应的元组元素上
...
BRA/CE342567
ESP/XDA205856
USA/31195855
>>> for country, _ in traveler_ids: ➏ # “_”占位符
... print(country)
...
USA
BRA
ESP
>>> b, a = a, b
>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])
>>> a, *body, c, d = range(5) # 这个变量可以出现在赋值表达式的任意位置
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)
metro_areas = [
('Tokyo','JP',36.933,(35.689722,139.691667)), # ➊
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.'))
fmt = '{:15} | {:9.4f} | {:9.4f}'
for name, cc, pop, (latitude, longitude) in metro_areas: # ➋我们把输入元组的最后一个元素拆包到变量构成的元组里,这样就获取了坐标。
if longitude <= 0: # ➌
print(fmt.format(name, latitude, longitude))
在切片和区间操作里不包含区间范围的最后一个元素是 Python 的风格,这个习惯符合 Python、C 和其他语言里以 0 作为起始下标的传统。这样做带来的好处如下:
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30] # 切片不会改变原有对象
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7] # del会改变原有对象
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100 # 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单独一个值
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: can only assign an iterable
# 每次迭代中都新建了一个列表
>>> board = [['_'] * 3 for i in range(3)] # 每次迭代中都新建了一个列表
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[1][2] = 'X'
>>> board
[['_', '_', '_'], ['_', '_', 'X'], ['_', '_', '_']]
# 3 个指向同一个列表的引用
>>> weird_board = [['_'] * 3] * 3 # 3 个指向同一个列表的引用
>>> weird_board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> weird_board[1][2] = 'O'
>>> weird_board
[['_', '_', 'O'], ['_', '_', 'O'], ['_', '_', 'O']]
# 3 个指向同一个列表的引用
row=['_'] * 3
board = []
for i in range(3):
board.append(row)
# 每次迭代中都新建了一个列表
>>> board = []
>>> for i in range(3):
... row=['_'] * 3 #
... board.append(row)
...
>>> board
[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]
>>> board[2][0] = 'X'
>>> board #
[['_', '_', '_'], ['_', '_', '_'], ['X', '_', '_']]
>>> t = (1, 2, [30, 40])
>>> t[2] += [50, 60]
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
不管是 list.sort 方法还是 sorted 函数,都有两个可选的关键字参数。
可选参数 key 还可以在内置函数 min() 和 max() 中起作用。另外,还有些标准库里的函数也接受这个参数,像itertools.groupby() 和 heapq.nlargest() 等
list.sort 方法会就地排序列表,sorted,它会新建一个列表作为返回值
>>> fruits = ['grape', 'raspberry', 'apple', 'banana']
>>> sorted(fruits)
['apple', 'banana', 'grape', 'raspberry']
>>> fruits
['grape', 'raspberry', 'apple', 'banana'] # 原列表并没有变化。
>>> sorted(fruits, reverse=True)
['raspberry', 'grape', 'banana', 'apple']
>>> sorted(fruits, key=len)
['grape', 'apple', 'banana', 'raspberry'] #因为这个排序算法是稳定,它们的相对位置跟在原来的列表里是一样的。
>>> sorted(fruits, key=len, reverse=True)
['raspberry', 'banana', 'grape', 'apple']
>>> fruits
['grape', 'raspberry', 'apple', 'banana']
>>> fruits.sort() # 对原列表就地排序,返回值 None 会被控制台忽略
>>> fruits
['apple', 'banana', 'grape', 'raspberry']
bisect 模块包含两个主要函数,bisect 和 insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素(待补充)
目前没遇到过编码问题,唯一值得有用的就是Unicode 三明治——目前处理文本的最佳实践
概述:函数本身就是对象,本质就是将函数对象的应用
一等对象定义:
接受函数为参数,或者把函数作为结果返回的函数是高阶函数
>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
>>> def reverse(word):
... return word[::-1]
>>> reverse('testing')
'gnitset'
>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
使用列表生成是代替map 和 filter等
使用sum代替reduce
all 和 any 也是内置的归约函数。
为了使用高阶函数,有时创建一次性的小型函数更便利。这便是匿名函数存在的原因
lambda 关键字在 Python 表达式内创建匿名函数。
然而,Python 简单的句法限制了 lambda 函数的定义体只能使用纯表达式。换句话说,lambda 函数的定义体中不能赋值,也不能使用 while和 try 等 Python 语句。在参数列表中最适合使用匿名函数
除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的 callable() 函数
>>> dir(factorial)
['__annotations__', '__call__', '__class__', '__closure__', '__code__',
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__get__', '__getattribute__', '__globals__',
'__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__',
'__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
__dict__ #与用户定义的常规类一样,函数使用 __dict__ 属性存储赋予它的用户属性
__annotations__ #dict 参数和返回值的注解
__call__ #method-wrapper 实现 () 运算符;即可调用对象协议
__closure__ #tuple 函数闭包,即自由变量的绑定(通常是 None)
__code__ #code 编译成字节码的函数元数据和函数定义体
__defaults__ #tuple 形式参数的默认值
__get__ #method-wrapper 实现只读描述符协议(参见第 20 章)
__globals__ #dict 函数所在模块中的全局变量
__kwdefaults__ #dict 仅限关键字形式参数的默认值
__name__ #str 函数名称
__qualname__ #str函数的限定名称,如 Random.choice
函数对象有个 defaults 属性,它的值是一个元组,里面保存着定
位参数和关键字参数的默认值。仅限关键字参数的默认值在
kwdefaults 属性中。然而,参数的名称在 code 属性中,它
的值是一个 code 对象引用,自身也有很多属性
注解不会做任何处理,只是存储在函数的 annotations 属性(一个字典)中:
>>> from clip_annot import clip
>>> clip.__annotations__
{'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
Python 的目标不是变成函数式编程语言,但是得益于 operator 和 functools 等包的支持,函数式编程风格也可以信手拈来
在函数式编程中,经常需要把算术运算符当作函数使用。例如,不使用递归计算阶乘。求和可以使用 sum 函数,但是求积则没有这样的函数。我们可以使用 reduce 函数(5.2.1 节是这么做的),但是需要一个函数计算序列中两个元素之积。示例 5-21 展示如何使用 lambda 表达式解决这个问题
from functools import reduce
def fact(n):
return reduce(lambda a, b: a*b, range(1, n+1))
operator 模块为多个算术运算符提供了对应的函数,从而避免编写lambda a, b: a*b 这种平凡的匿名函数
from functools import reduce
from operator import mul
def fact(n):
return reduce(mul, range(1, n+1))
operator 模块中还有一类函数,能替代从序列中取出元素或读取对象属性的 lambda 表达式:因此,itemgetter 和 attrgetter 其实会自行构建函数。(评价)
functools.partial 这个高阶函数用于部分应用一个函数。部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少
>>> from operator import mul
>>> from functools import partial
>>> triple = partial(mul, 3) ➊
>>> triple(7) ➋
21
>>> list(map(triple, range(1, 10))) ➌
[3, 6, 9, 12, 15, 18, 21, 24, 27]
作者从策略模式开始,讨论了一等函数在设计模式中的角色,并用一等函数简化了设计模式的实现方式,以此来展示 Pythonic 的设计模式应该是什么样子的
没有什么新的内容,对python的设计模式请看我自己将要写的python设计模式的博客