《流畅的Python》读书总结:第一章,第二章

一,Python的数据类型

1,双下方法--- __getItem__()
说明:特殊方法是为了给python解释器调用,开发者不需要自己调用,但是可以通过实现特殊方法,来提高效率
例子:obj[key],实际上是调用obj的私有方法__getItem__()len(xx)本质上也是调用数据类型的__len__()方法
应用1:通过重写特殊方法,可以使自定义类通过 +-*/计算等等,比如__repr__,__abs__,__bool__,__add__,__mul__,__rmul__(bool(x)调用__bool__,如果没有调用的是__len__)
应用2:重写or增加双下方法。当使用自定义的类obj[key]去调用属性的时候,是不支持的,可以通过给类增加__getItem__()方法,使其支持,并自定义获取属性的逻辑;否则,只能使用getattr(self, key)
示例代码
应用3:编写信号函数:编写自己的库时,比如功能被关闭,可以通过自定义__exit__()方法触发特定的函数,此函数作为overwrite的API开放,就会触发退出信号

# 例一,获取属性方法,重写函数__getitem__
class Test():
    def __init__(self):
        self.a=1
        self.b=2
        print(self['b'])  # 2 ,如果没有__getitem__方法,此处会报错
    def __getitem__(self, x):
        return getattr(self, x)

t = Test()
print(t['a'])   # 1
print(t.a)   # 1

# 例二,len(),deck[0],重写函数__len__,__getitem__
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)]+list('JQKA')
    suits = 'spades diamonds clubs hearts'.split( )
    def __init__(self):
        self._cards = [Card(rank, suit) for suit in self.suits
                                        for rank in self.ranks]
    def __len__(self):
        return len(self._cards)
    def __getitem__(self, position):
        return self._cards[position]

>>> from random import choice
>>> choice(deck)
Card(rank='3', suit='hearts')
>>> choice(deck)
Card(rank='K', suit='spades')
>>> choice(deck)
Card(rank='2', suit='clubs')

好处:统一标准,既能复用轮子,也可通过定义自定义类中的方法,方便用户使用
另外,实现了__getitem__方法之后。自定义deck类还支持切片操作,也变成了可迭代的(in运算符也会引起迭代,所以也支持in运算符,以及random.choice、reversed和sorted等等)。

注:如果x是一个内置类型的实例,那么len(x)的速度会非常快。背后的原因是CPython会直接从一个C结构体里读取对象的长度,完全不会调用任何方法。获取一个集合中元素的数量是一个很常见的操作,在str、list、memoryview等类型上,这个操作必须高效

二,数据结构(序列数据)

Python也从ABC那里继承了用统一的风格去处理序列数据这一特点,不管是哪种数据结构,字符串、列表、字节序列、数组、XML元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片、排序,还有拼接。

几种序列分类
1,容器序列与扁平序列
容器序列存放的是它们所包含的任意类型的对象的引用,而扁平序列里存放的是值而不是引用。换句话说,扁平序列其实是一段连续的内存空间

  • 容器序列:list、tuple和collections.deque这些序列能存放不同类型的数据
  • 扁平序列:str、bytes、bytearray、memoryview和array.array,这类序列只能容纳一种类型
    2,可变序列与不可变序列
  • 可变序列:list、bytearray、array.array、collections.deque和memoryview
  • 不可变序列:tuple、str和bytes
    序列UML类图:sequence不可变序列,MutableSequence可变序列

    从上图可以看出,可变序列MutableSequence继承自不可变序列,又实现了__setitem__,__delitem__等等修改序列的方法。通过记住这些类的共有特性,把可变与不可变序列或是容器与扁平序列的概念融会贯通

数列数据一:列表推导和生成器表达式

前者是列表list生成的快捷方式,而生成器表达式用于创建任何类型的序列。

一,列表推导
word = 'fwfsd'
# 1,循环添加
codes=[]
for code in word:
  codes.append(code)
# 2,列表推导
codes = [ code for code in word ]

灵活运用列表推导,一般只建议用作生成list
列表推导使用注意:

  • 变量泄漏:python2.x中存在变量泄露,python3.x中,列表推导不存在变量泄露,放心使用。
    列表推导、生成器表达式,以及同它们很相似的集合(set)推导和字典(dict)推导,在Python 3中都有了自己的局部作用域,就像函数似的
  • 笛卡尔积,等排列组合生成复杂类型内容的list(注意嵌套关系
>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes]  # 注意嵌套关系※※※
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'),
 ('white', 'M'), ('white', 'L')]
  • 与filter,map比较:
>>> 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]
二,生成器表达式

列表推导可以用来初始化元组、数组或其他序列类型,但是生成器表达式是更好的选择。
生成器表达式 → 遵守了迭代器协议,可以逐个地产出元素,节省内存

生成器表达式和列表推导语法相同,仅符号由中括号变为括号

>>> 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])
# 1,如果生成器表达式是一个函数调用过程中的唯一参数,那么不需要额外再用括号把它围起来
# 2,array的构造方法需要两个参数,因此括号是必需的
三,元组

不仅仅是不可变的列表,可用于没有字段名的记录
1,元组拆包 -->> 其实是可迭代元素拆包:
元组拆包可以应用到任何可迭代对象上,唯一要求是等式两边数量一致,或者用*表示多余的元素。

a = (1,2,3)
b,c,d = a  # 第一种
print('%d, %d, %d'%a)  # 第二种
b,a=a,b # 不使用中间变量,交换a,b的值
function(*a) # 第三种
# *前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置
a, *b, c, d = range(5)  # (0, [1,2], 3, 4)

2,嵌套拆包
同上拆包,只要等号左右结构相符合,既可以正确的对应赋值
3,具名元组
使用collectis.namedtuple创建具名元组,类似一个类,创建类的时候,传入类名和各个元素名称,然后实例化这个类,同时传入对应参数的值

>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')  ➊
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))  ➋
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722,
139.691667))
>>> tokyo.population  ➌
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'

3,作为不可变列表--元组
除了跟增减元素相关的方法之外,元组支持列表的其他所有方法

四,切片

1,普通切片操作

l = [1,2,3,4,5]
l[:2]  # [1,2]
l[3:]  # [4,5]
l[::2]  #[1,3,5]
l[::-1]  #[5,4,3,2,1]

2,多维切片--numpy扩展
[]运算符里还可以使用以逗号分开的多个索引或者是切片,外部库NumPy里就用到了这个特性,二维的numpy.ndarray就可以用a[i, j]这种形式来获取,抑或是用a[m:n, k:l]的方式来得到二维切片.

要正确处理这种[]运算符的话,对象的特殊方法getitemsetitem需要以元组的形式来接收a[i, j]中的索引。在自定义类中实现需注意

python标准库中并不支持多维切片,因为数据都是一维的,numpy中支持多维切片。

3,切片赋值
切片可以作为左值来赋值,也可以用del()操作
注意:如果切片在左值,右值必须是个可迭代对象

l = list(range(10))    # [0,1,2,3,4,5,6,7,8,9]
l[2:6] = [20,30]
print(l)    #  [0,1,20,30,6,7,8,9]
del(l[5:7])   # [0,1,20,30,6,9]

l[2:4] = 10  # err
l[2:4] = '10123'  # right

4,对序列使用+和*
+*不修改原有对象二生成全新队列
注:用*扩展的列表中是引用对象的话,那么新生成的是多个引用,指向同一个对象

l = [1,2]
d = [3,4]
l+d    #  [1,2,3,4]
d+l    # [3,4,1,2]
l * 3   #  [1,2,1,2,1,2]

lis = [['_'] * 3 for i in range(3)]  # [ ['_','_','_',], ['_','_','_',] ,['_','_','_',] ]
lis[2][1] = 6                       # [ ['_','_','_',], ['_','_','_',] ,['_','6','_',] ]

lis = [['_'] * 3] * 3  # [ ['_','_','_',], ['_','_','_',] ,['_','_','_',] ]
lis[2][1] = 6          # [ ['_','6','_',], ['_','6','_',] ,['_','6','_',] ]

5,序列的增量赋值 +=,*=
底层调用的方法为__iadd__,是一种“就地加法”,可变序列一般都实现了__iadd__方法
如果实现了__iadd__,+=的可变序列,就会“就地改动”
如果没有,a+=b,就和a=a+b一样,先生成a+b的结果对象,再赋值给a

# 可变序列,就地改动
l = [1,2,3]
id(l)      # 2269445509512
l *= 2     # [1,2,3,1,2,3]
id(l)      # 2269445509512

# 不可变序列,新的对象
a = (1,2,3)
id(a)      # 2269449062184
a *= 2     # (1,2,3,1,2,3)
id(a)      # 2269447579496

不建议对不可变序列使用 +=,*=,存在创建对象和赋值,可能会影响效率

语言坑点:(边界情况)对于元组的修改:

t = (1,2,[3,4])
t[2] += [6,6]

运行上面代码,会产生两个结果:1,t被修改为(1,2,[3,4,6,6]),同时抛出TypeError异常。
使用dis模块,查看Python字节码,查看执行顺序:

  • 获取t[2] 存入TOS(Top Of Stack,栈的顶端)
  • 计算TOS += [6,6],成功,因为TOS里是list
  • 赋值t[2] = TOS,抛出异常

故:

  • 尽量不要把可变对象放在元组内,容易产生不易排查的bug
  • 增量赋值不是一个原子操作
  • 学习查看Python字节码

6,排序:list.sort方法和内置函数sorted
list.sort方法会就地排序列表,也就是说不会把原列表复制一份;这个方法的返回值是None;
内置函数sorted则相反,返回一个新建列表,可以接受任何形式的可迭代对象作为参数,甚至包括不可变序列或生成器
两个方法的共有参数:reverse,key(筛选函数,仅接受一个参数,返回一个参数,处理每个元素,以返回值作为排序参数,默认返回元素本身)

7,用bisect来管理已排序的序列(略)

bisect模块包含两个主要函数,bisect和insort,两个函数都利用二分查找算法来在有序序列中查找或插入元素

8,当列表不是首选时

  • 要存放1000万个浮点数的话,数组(array)的效率要高得多,因为数组在背后存的并不是float对象,而是数字的机器翻译
  • 如果需要频繁对序列做先进先出的操作,deque(双端队列)的速度应该会更快(不需要内存移动)
  • 包含操作(比如检查一个元素是否出现在一个集合中)的频率很高,用set(集合)会更合适

数组Array:
如果我们需要一个只包含数字的列表,那么array.array比list更高效。数组支持所有跟可变序列有关的操作,包括.pop、.insert和.extend。另外,数组还提供从文件读取和存入文件的更快的方法,如.frombytes和.tofile。

另外一个快速序列化数字类型的方法是使用pickle模块。pickle.dump处理浮点数组的速度几乎跟array.tofile一样快。不过前者可以处理几乎所有的内置数字类型,包含复数、嵌套集合,甚至用户自定义的类。前提是这些类没有什么特别复杂的实现。

memoryview:
是一个内置类,能用不同的方式读写同一块内存数据,而且内容字节不会随意移动。
利用memoryview和struct来操作二进制序列

NumPy和SciPy
凭借着NumPy和SciPy提供的高阶数组和矩阵操作,Python成为科学计算应用的主流语言。NumPy实现了多维同质数组(homogeneous array)和矩阵,这些数据结构不但能处理数字,还能存放其他由用户定义的记录。通过NumPy,用户能对这些数据结构里的元素进行高效的操作。SciPy是基于NumPy的另一个库,它提供了很多跟科学计算有关的算法,专为线性代数、数值积分和统计学而设计。SciPy的高效和可靠性归功于其背后的C和Fortran代码,而这些跟计算有关的部分都源自于Netlib库

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342