在读书籍:《流畅的Python》

持续阅读并更新

本书对Python的数据模型/类型和一些基础有一些深入的讲解。翻译自国外的书籍《Fluent Python》,最早2017年出版,我大概是18年买的,那阵子Python刚开始火,不过买了发现过了好几年都看不懂,现在勉强能抽空看看hh...

前言

Python3官方教程开头是这样写的:”Python是一门既容易上手又强大的编程语言。”这句话本身并无大碍,但正因为它这一特性,很多人都只用到了其强大功能的一小部分...

作者本书强调Python的独有特性,专注于基本库。作者表明尽管Python的包索引已经极多,但是他几乎不会提到标准库以外的任何包。

赞!因为接触过一段时间的工作会发现三方包的性能方面参差不齐,实际中需要代码优化和加速的场景越来越频繁,而如果能够多调用标准库,充分发挥Python特性,我想应该是可以有助于代码运行效率提高的。

第一部分-Python数据模型

浅浅介绍一下Python一些数据类型/类的底层构造模式

要点

  1. Python中的内置类型实现了很多特殊方法(以双下划线开头如__getitem__()),也就是函数,来实现一些比较基本的操作,如:迭代/循环、属性访问、对象实例创建删除、字符串表示,文件管理(with...)等
  2. 所以当需要使用内置类型实现上述相关功能时,用内置的方法:速度更快,优先考虑,如len(x),若x是一个内置类型如list的实例,那么运行就快,相当于走后门
  3. 下一部分的主要目的就是熟悉和了解Python内置的数据类型的底层特性及内置方法:序列(列表、元组)、字典、集合等,特殊用法,从而提高代码的性能

数据结构——序列(列表+元组...)

第二部分是数据结构,包含三部分

1 序列和数组; 2 字典和集合; 3 文本和字节序列

list和推导式

  1. 序列就是一个能装很多类似数据的可以遍历的数据,比如list可以装很多个元素,string可以有逐字符遍历。
  2. 序列分类
    1. 按能不能放不同类型的数据分为:容器序列(list tuple...)、扁平序列(str array.array...),后者小而快,前者更灵活
    2. 按是否可变(简单理解为自由增删改)分可变和不可变,如list dict属于可变str tuple不可变
  3. list使用要点:
    1. 多使用列表推导式,但一般只用于创建新列表,一行不能搞定的也不用
    2. 生成器-列表推导,将列表推导的[]换成()就得到了生成器,生成器就是动态吐出元素不会直接创建,元素在调用时再生成,一次性的,每个只能吐一次
      • 节省内存,避免额外内存消耗,大数据考虑

元组tuple及*的使用

  1. 元组相当于一行记录,同属一个元组的这些数据是隐形绑定的,相当于表格的一行
  2. 元组拆包的妙用:显式或隐式的都有,很方便,举例:
1
2
3
4
5
6
7
8
9
# 平行复制
loc = (33.9, 118.5)
lat, lng = loc
# 函数传参
afunc = f(a,b)
z = f(*loc)
# *处理不确定个数的元素
>>> a,*b,c,d = range(5)
>>> b = [1,2]
  1. 具名元组,可以用来构建:
    1. 带字段的元组。表头?是否可以取代表格数据管理还有待探索hh
    2. 有名字的类
1
2
3
from collections import namedtuple
City = namedtuple('City', 'name country pop loc') #创建类
tolyo = City('Tokyo','JP',36.933, (35.7, 139.7)) # 实例化

切片与赋值

即选取序列中的一段/一部分

索引从0开始,左闭右开 (a,b],空表示断点

  • s[1:5],选取序列s的第二个到第五个元素
  • s[a: b:c],a~b之间以间隔c取值, c值为负数代表倒序取值

切片赋值时,若左侧为一个切片,右侧应该是可迭代的,即便只有一个值

1
alistp[1:5]=[48]

序列的拼接

+和*都是创建一个新的序列:

1
2
3
4
5
a = [1,2,3]
b = [3,4,5]
c = a+b # >>>[1,2,3,3,4,5]
d = a*3 # >>>[1, 2, 3, 1, 2, 3, 1, 2, 3]
e = 5*'abc'# >>>'abcabcabcabcabc'

+=和*=可用于对可变序列,如列表和字典,其原理是这些类的内部实现了__iadd__或__imul__

  • 对于可变序列使用时,就地改变数据,数据的内存及ID位置不会变

序列排序

list.sort():就地排序,返回None

sorted():对可迭代对象排序生成新对象,如列表、元组及生成器都可以

  • 可选参数:reverse(默认是False,升序输出); key 默认是元素本身的值排序,若设置key=str.lower或key=len可以使其忽略大小写或基于长度排序

  • 此外,key关键字可以在很多内置函数中使用,如max() min()

当我们需要进行查找和插入元素并保持序列的顺序不变时,可使用bisect模块,其使用二分法进行查找和插入,有需要时再检索即可

处理数值序列-数组array

如果只处理纯数字,尤其是比如大量的浮点数,array比list要高效地多

因为数组背后是对数字的机器翻译——字节标段数

使用数组array

如以下示例生成一个含1000万的浮点数组并写入文件,再读取。会发现写入不到1s,读取约0.1s,文件大小<80mb

1
2
3
4
5
6
7
8
9
10
11
12
from array import array
from random import random
floats = array('d', (random() for i in range(10**7))) #d代表底层C语言数据类型,浮点, 更多类型可查看文档
# 写入
fp = open('test_floats.bin','wb')
floats.tofile(fp)
fp.close()
# 读取
floats2 = array('d')
with open('test_floats.bin','rb') as fp:
floats2.fromfile(fp, 10**7)
floats2==floats
  • 此外,pickle也是一个快速序列化数字类型的内置模块,同时pickle还可以序列化各种类型的数据

  • array的排序可以通过, a = array.array(a.typecode, sorted(a))

标准库之外:值得学习的Numpy和Scipy库,强大的科学计算工具

Python内部实现了双向队列的类型collections.deque:线程安全、可以快速从两段添加或删除元素的数据类型

数据结构——字典与集合

字典功能结构

字典的构造和推导式

字典常用方法

键找不到时的处理

常规查找:

my_dict.get(key, defalut)

更新处理

1
2
3
4
5
my_dict.setdefault(key, []).append(newvalue)
# 等价于
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(newvalue)

自定义字典

little tips

  1. “_”占位符可以用来过滤一些不需要的输出结果