一,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]的方式来得到二维切片.
要正确处理这种[]运算符的话,对象的特殊方法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,序列的增量赋值