2021年6月2日——yaco
流畅的Python1-4章内容
第1章 Python数据模型
- Python中存在大量的魔方方法,让Python对象使用起来变得非常简单
- Python中可以使用一些特殊方法让自定义数据类型表现得跟内置类型一样,从而写出更完美的代码
- 常见的魔方方法有(
__init__
,__lt__
,__len__
)这些特殊方法是为了被python解释器调用的, 这些方法会注册到他们的类型中方法集合中, 相当于为cpython提供抄近路. 这些方法的速度也比普通方法要快, 当然在自己不清楚这些魔术方法的用途时, 不要随意添加. - 关于字符串的表现形式是两种,
__str__
与__repr__
. python的内置函数repr
就是通过__repr__
这个特殊方法来得到一个对象的字符串表示形式. 这个在交互模式下比较常用, 如果没有实现__repr__
, 当控制台打印一个对象时往往是<A object at 0x000>
. 而__str__
则是str()
函数时使用的, 或是在print
函数打印一个对象的时候才被调用, 终端用户友好. - 两者还有一个区别, 在字符串格式化时,
"%s"
对应了__str__
. 而"%r"
对应了__repr__
.__str__
和__repr__
在使用上比较推荐的是,前者是给终端用户看,而后者则更方便我们调试和记录日志. - 更多的特殊方法: https://docs.python.org/3/reference/datamodel.html
第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标准中 e
和 e\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 字符时, 得到的结果可能会不尽人意.
网友评论