流畅的Python阅读笔记(一)

2021年6月2日——yaco
流畅的Python1-4章内容

第1章 Python数据模型

第2章 序列构成的数组

2.1 内置序列类型概览

这部分主要是介绍序列, 着重介绍数组和元组的一些高级用法,序列按照容纳数据的类型可以分为:

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

如果按照是否能被修改可以分为:

  • 可变序列: list、bytearray、array.array、collections.deque 和 memoryview
  • 不可变序列: tuple、str 和 bytes

2.2 列表推导

列表推导是构建列表的快捷方式, 可读性更好且效率更高.

例如, 把一个字符串变成unicode的码位列表的例子, 一般:

symbols = '$¢£¥€¤'
codes = []
for symbol in symbols:
    codes.append(ord(symbol))

使用列表推导:

symbols = '$¢£¥€¤'
codes = [ord(symbol) for symbol in symbols]

能用列表推导来创建一个列表, 尽量使用推导, 并且保持它简短.

2.3 笛卡尔积与生成器表达式

生成器表达式是能逐个产出元素, 节省内存. 例如:

>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes):
... print(tshirt)

实例中列表元素比较少, 如果换成两个各有1000个元素的列表, 显然这样组合的笛卡尔积是一个含有100万元素的列表, 内存将会占用很大, 而是用生成器表达式就可以帮忙省掉for循环的开销.

2.4 具名元祖

元组经常被作为不可变列表的代表. 经常只要数字索引获取元素, 但其实它还可以给元素命名:

>>> 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'

2.5 切片

列表中是以0作为第一个元素的下标, 切片可以根据下标提取某一个片段.

s[a:b:c] 的形式对 s 在 a 和 b 之间以 c 为间隔取值。c 的值还可以为负, 负值意味着反向取值.

>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::-2]
'eccb'

第3章 字典和集合

3.1 泛映射类型

dict类型不但在各种程序里广泛使用, 它也是Python语言的基石. 正是因为dict类型的重要, Python对其的实现做了高度的优化, 其中最重要的原因就是背后的散列表,set(集合)和dict一样, 其实现基础也是依赖于散列表.

散列表也叫哈希表, 对于dict类型, 它的key必须是可哈希的数据类型. 什么是可哈希的数据类型呢, 它的官方解释是:

如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 __hash__() 方法。另外可散列对象还要有__qe__() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的……

str, bytes, frozenset 和数值都是可散列类型.

3.2 字典推导式

Pyton中,可以利用字典推导快速生成字典

DIAL_CODE = [
    (86, 'China'),
    (91, 'India'),
    (7, 'Russia'),
    (81, 'Japan'),
]

country_code = {country: code for code, country in DIAL_CODE}
print(country_code)

'''
OUT:
{'China': 86, 'India': 91, 'Russia': 7, 'Japan': 81}
'''

3.3 defaultdict:处理找不到的键的一个选择

当某个键不在映射里, 我们也希望也能得到一个默认值. 这就是 defaultdict , 它是 dict 的子类, 并实现了 __missing__ 方法.

import collections
index = collections.defaultdict(list)  # 默认为列表
nums = [1, 2, 3, 4]
for item in nums:
    key = item % 2
    index[key].append(item)
    
index
>>> defaultdict(list, {1: [1, 3], 0: [2, 4]})

3.4 字典的变种

标准库里 collections模块中,除了 defaultdict之外的不同映射类型:

  • OrderDict: 这个类型在添加键的时候,会保存顺序,因此键的迭代顺序总是一致的(qiskit-metal中用到了)
  • ChainMap: 该类型可以容纳数个不同的映射对像,在进行键的查找时,这些对象会被当做一个整体逐个查找,直到键被找到为止 pylookup = ChainMap(locals(), globals())
  • Counter: 这个映射类型会给键准备一个整数技术器,每次更行一个键的时候都会增加这个计数器,所以这个类型可以用来给散列表对象计数,或者当成多重集来用.
import collections
ct = collections.Counter('abracadabra')
print(ct)   # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
ct.update('aaaaazzz')
print(ct)   # Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(ct.most_common(2)) # [('a', 10), ('z', 3)]
  • UserDict: 这个类其实就是把标准 dict 用纯 Python 又实现了一遍
import collections
class StrKeyDict(collections.UserDict):
    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]
        
    def __contains__(self, key):
        return str(key) in self.data
        
    def __setitem__(self, key, item):
        self.data[str(key)] = item

3.5 不可变映射类型

说到不可变, 第一想到的肯定是元组, 但是对于字典来说, 要将key和value的对应关系变成不可变, types 模块的 MappingProxyType 可以做到:

from types import MappingProxyType
d = {1:'A'}
d_proxy = MappingProxyType(d)
d_proxy[1]='B' # TypeError: 'mappingproxy' object does not support item assignment

d[2] = 'B'
print(d_proxy) # mappingproxy({1: 'A', 2: 'B'})

d_proxy 是动态的, 也就是说对 d 所做的任何改动都会反馈到它上面.

3.6 集合论

集合的本质是许多唯一对象的聚集. 因此, 集合可以用于去重,集合中的元素必须是可散列的, 但是set本身是不可散列的, 而frozenset本身可以散列.

集合具有唯一性, 与此同时, 集合还实现了很多基础的中缀运算符. 给定两个集合 a 和 b, a | b 返回的是它们的合集, a & b 得到的是交集, 而 a - b 得到的是差集.

合理的利用这些特性, 不仅能减少代码的数量, 更能增加运行效率.

# 集合的创建
s = set([1, 2, 2, 3])

# 空集合
s = set()

# 集合字面量
s = {1, 2}

# 集合推导
s = {chr(i) for i in range(23, 45)}

第4章 文本和字节序列

本章讨论了文本字符串和字节序列, 以及一些编码上的转换. 本章讨论的 str 指的是python3下的.

4.1 字符串问题

字符串是个比较简单的概念: 一个字符串是一个字符序列. 但是关于 "字符" 的定义却五花八门, 其中, "字符" 的最佳定义是 Unicode 字符。因此, python3中的 str对象中获得的元素就是 unicode 字符.

把码位转换成字节序列的过程就是编码, 把字节序列转换成码位的过程就是解码 :

>>> s = 'café'
>>> len(s)
4 
>>> b = s.encode('utf8') 
>>> b
b'caf\xc3\xa9'
>>> len(b)
5 
>>> b.decode('utf8') #'café

码位可以认为是人类可读的文本, 而字符序列则可以认为是对机器更友好. 所以要区分 .decode().encode() 也很简单. 从字节序列到人类能理解的文本就是解码(decode). 而把人类能理解的变成人类不好理解的字节序列就是编码(encode).

4.2 字节概要

python3有两种字节序列, 不可变的 bytes 类型和可变的 bytearray 类型. 字节序列中的各个元素都是介于 [0, 255]之间的整数.

4.3 处理编码问题

python自带了超过100中编解码器. 每个编解码器都有一个名称, 甚至有的会有一些别名, 如 utf_8 就有 utf8, utf-8, U8 这些别名.

如果字符序列和预期不符, 在进行解码或编码时容易抛出 Unicode*Error 的异常. 造成这种错误是因为目标编码中没有定义某个字符(没有定义某个码位对应的字符), 这里说说解决这类问题的方式.

  • 使用python3, python3可以避免95%的字符问题.
  • 主流编码尝试下: latin1, cp1252, cp437, gb2312, utf-8, utf-16le
  • 留意BOM头部 b'\xff\xfe' , UTF-16编码的序列开头也会有这几个额外字节.
  • 找出序列的编码, 建议使用 codecs 模块

4.4 规范化unicode字符串

s1 = 'café'
s2 = 'caf\u00e9'

这两行代码完全等价. 而有一种是要避免的是, 在Unicode标准中 ee\u0301 这样的序列叫 "标准等价物"。 这种情况用NFC使用最少的码位构成等价的字符串:

>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> len(s1), len(s2)
(4, 5)
>>> s1 == s2
False

改进后:

>>> from unicodedata import normalize
>>> s1 = 'café' # 把"e"和重音符组合在一起
>>> s2 = 'cafe\u0301' # 分解成"e"和重音符
>>> len(s1), len(s2)
(4, 5)
>>> len(normalize('NFC', s1)), len(normalize('NFC', s2))
(4, 4)
>>> len(normalize('NFD', s1)), len(normalize('NFD', s2))
(5, 5)
>>> normalize('NFC', s1) == normalize('NFC', s2)
True
>>> normalize('NFD', s1) == normalize('NFD', s2)
True

4.5 unicode文本排序

对于字符串来说, 比较的码位. 所以在非 ascii 字符时, 得到的结果可能会不尽人意.

你可能感兴趣的:(流畅的Python阅读笔记(一))