一,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
从上图可以看出,可变序列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]
的方式来得到二维切片.
要正确处理这种[]运算符的话,对象的特殊方法getitem和setitem需要以元组的形式来接收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库