Scott's Blog

学则不固, 知则不惑

0%

Python 中的内置类型与数据结构

内置类型与元组、数组、字典、集合,及常用操作。

内置类型

Python 中的内置类型

  • None: 全局只有一个.
  • 数值类型:int, float, complex 复数类型, bool.
  • 迭代类型:迭代器、生成器,可以用 for 循环遍历。
  • 序列类型:list, (bytes, bytearray, memoryview), range, tuple, str, array.
  • 映射:字典,有 key 和 value
  • 集合:set,frozenset(不可修改的 set),set 和 dict 的实现原理差不多,性能很高。
  • 上下文管理器:with 语句。
  • 其他:模块类型、class 和实例类型、函数类型、方法类型、代码类型、object对象、type类型、ellipsis 类型(省略号), notimplemented类型。

元组

性质

元组内部元素确定以后,不可修改;如果元组里可以放数组,数组的元素是可以修改的,创建一个元组:

1
tup = 4,5,6

嵌套元组:

1
tup = (4,5,6),(7,8,9)

其实等于:

1
tup = ((4,5,6),(7,8,9))

任何序列或可迭代的对象都可以转化为元组:

1
2
tuple([3,4,5])
tuple('string')

通过tuple[]这种方式起来访问元组的内容。

元组可以相加,变成一个更长的元组,即a+b = ab;也可相乘,即ab*4 = abababab

解包

如果你尝试将元组赋值给多个变量,Python会尝试把元组拆开分别赋值给这些变量(即解包)。

1
2
3
tup = (1,2,3)
a,b,c = tup

这时候,a = 1, b = 2, c = 3;

嵌套的元组也是可以的:

1
2
3
tup = (1,2,(3,4))
a,b,(c,d) = tup

这时候,d = 4;

在别的语言中,如果你要交换两个元素,需要这样操作:

1
2
3
tmp = a
a = b
b = tmp

python里面可以这样

1
b,a = a,b

解包还有一些其他的用法,比如按照元组的数据结构解包:

1
2
3
seq = [(1,2,3),(4,5,6),(7,8,9)]
for a,b,c in seq:
print('a={},b={},c={}'.format(a,b,c))

只提取前面几个元素,后面的放一起:

1
2
3
values = 1,2,3,4,5
a,b,*rest = values

元组有一个方法,可以统计特定的值在元组中出现的次数:

1
2
a = (1,2,2,3)
a.count(2) # 输出2,因为2出现了两次

命名元组

这部分为高级内容,看不懂的可以跳过。

使用命名元组需要先导入,假设我们有一个故事Stock,里面有股票的名字,股份数量shares,还有股价price,那我们就可以设置一个命名元组如下:

1
2
from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price'])

同时我们可以定义一个函数用来计算市值,可以注意到,在这个函数内部,我们创建了一个命名元组对象s,有了这个对象以后,我们就可以直接调用s的shares和price属性:

1
2
3
4
5
6
def compute_cost(records):
total = 0.0
for rec in records:
s = Stock(*rec)
total += s.shares * s.price
return total

我们也可以直接这样操作:

1
2
3
apple = Stock('name',100,297)
apple.shares * apple.price

需注意这样会报错:

1
compute_cost('apple',100,297) ---> takes 1 positional argument but 3 were given

命名元祖创建后不可修改,比如 apple.shares = 10 , 会报错。如果实在要改,可以使用apple = apple._replace(shares=10),如果你的目标是定义一个需要更新很多实例属性的高效数据结构,那么命名元组并不是你的最佳选择。 这时候你应该考虑定义一个包含 slots 方法的类。 # 数组

数组相信大家都不陌生,数组可以包含None值:

1
a_list = [1,2,None]

可以从元组转化而来:

1
2
tup = ('foo','bar','baz')
b_list = list(tup)

可以按照索引来修改数组的值:

1
b_list[1] = 'peekaboo'

list可以直接转化生成器或者迭代器:

1
2
3
4
gen = range(10)
gen # 输出range(0,10)

list(gen) # 输出[0,1,2,3,4,5,6,7,8,9,10]

添加、删除

使用append方法即可往数组添加元素:

1
2
values = ['a','b']
values.append('c')

也可以使用insert方法向指定的位置插入元素:

1
2
values.sert(1,'-')
values 输出# ['a','-','b','c']

insert比append要花费更多的资源,因为insert后,其后面的元素都要挪位置,如果你要在数组的首尾操作,可以去看看collections.deque

相反的,我们可以使用pop方法来取出指定位置的元素, 这时候后面的元素也都会往前面移动:

1
values.pop(0) # a

也可以直接根据值来删除某个元素:

1
values.remove('b')

检查某个元素是否在数组中:

1
2
'a' in ['a','b','c']
'a' not in ['a','b','c']

在数组是否包含某个元素,比字典和sets要慢的多。

运算

数组的相加,即首尾相连,a+b = ab; 数组的相加需要创建新的数组,还要进行复制运算,所以耗费的资源会比较多,所以我们一般使用extend方法:

1
2
['a']+['b'] # ['a','b']
['a'].extend(['b'])

排序

你可以直接排序一个数组,且不需要创建一个新的数组,只需要调用sort方法即可:

1
2
3
a = [7,2,1]
a.sort()
a #输出 [1,2,7]

sort接收第二个参数,可以定义sort的条件,比如我们通过字符的长度来排序:

1
2
b = ['saw','small','he','six']
b.sort(key=len)

二分查找与操作

python内置了一个bisect模块,实现了基于二分查找的算法,在列表中查找元素可以使用 list.index() 方法,其时间复杂度为O(n)。对于大数据量,则可以用二分查找进行优化,二分查找也成为折半查找,算法每一次比较都使搜索范围缩小一半, 其时间复杂度为 O(logn),需要注意,二分查找要求对象必须有序。

1
2
3
4
c = [1,2,2,2,3,4,7]
bisect.bisect(c,2) # 返回会插入的位置,即索引4
bisect.bisect(c,5) # 返回5
bisect.insort(c,6) # 插入到4后面

切片

通过将形如start:stoped的格式传给[],就可以在序列对象中切出你想要的部分,举例:

1
2
seq = [6,5,3,5,7,2]
seq[2:4] # 输出 [3, 5]

切片也可以赋值,比如:

1
2
seq[2:4] = [0,0]
seq # 输出 [6, 5, 0, 0, 7, 2]

如果你要选择之前或者之后的所有值,将一边省略即可,比如我要选择index=3之后的所有值,即[3:]。

你甚至可以指定步长,也就是隔多远选一个值,例如:

1
2
3
seq = [6,5,3,5,7,2]
seq[::2] # 输出 [6, 3, 7]
seq[::3] # 输出 [6, 5]

如果你想将数组反转,将步长设为-1试试:

1
2
seq = [6,5,3,5,7,2]
seq[::-1] # 输出[2, 7, 5, 3, 5, 6]

内置的序列函数

enumerate

在用for in循环一个序列的时候,我们经常想要跟踪index,为此你可能会声明一个i来记录:

1
2
3
4
i = 0
for v in values:
...
i+=1

不过python已经内置了一个函数,可以帮到你:

1
2
for index,value in enumerate(['a','b','c']):
print(value,index)

这里的index就是你想要的那个i了。enumerate在将列表转化为字典的时候尤其方便:

1
2
3
4
5
6
some_list = ['for','bar','baz']
mapping = {}
for i,v in enumerate(some_list):
mapping[v] = i

#输出: {'bar':1,'baz':2,'foo':0}

sorted

sorted函数接收一个序列对象,排序后返回一个新的。

zip

zip会将两个序列中的元素一一对应。

1
2
3
4
seq1 = ['scott','frank','jason']
seq2 = ['1','2','3']
zipped = zip(seq1,seq2)
list(zipped) # 输出: [('scott','1'),('frank','2'),('jason','3')]

zip可以接受3个序列一一对应,对应关系按照最短的处理。

将如上函数结合,可以实现一些很骚的写法,看下面的例子,将序列结合并按顺序打印:

1
2
3
for i (a,b) in enmerate(zip(se1,seq2)):
print('{}:{},{}'.format(i,a,b))

统计名和姓:

1
2
3
4
5
6
7
pitchers = [('Nolan', 'Ryan'), ('Roger', 'Clemens'),('Schilling', 'Curt')]

first_names, last_names = zip(*pitchers)

first_names # ('Nolan', 'Roger', 'Schilling')

last_names # ('Ryan', 'Clemens', 'Curt')

reversed

1
list(reversed(range(10)))

reversed返回一个生成器,如果你想要取出其中数据,需要用list包装一下。

字典

字典是python中最重要的数据结构之一,创建dict很简单,直接使用花括号就创建了一个dict。

1
d = {}

增加元素:

1
2
d[1] = 'hi'
d['one'] = 1

检查字典中是否有某个key(检查的不是values,而是key)

1
1 in d

删除字典中的值,可以用del命令,或者pop方法:

1
2
3
4
5
6
7
d = {}
d['v'] = 10
d['C'] = 11
del d['v']

ret = d.pop('C')
ret # 11

pop方法会返回删除的元素。

可以直接使用d.keys(),和d.values()方法访问dict的所有keys和values。

使用d.update()方法,可以将两个dict合并。

从两个序列创建一个字典,你也许会准备一个index列表和values列表,再用for循环手动创建,大可不必。你可以直接使用dict+zip函数来定义:

1
mapping = dict(zip(indexs,values))

默认值

检测dict是否拥有某个值,如果有就取出来,没有就设置默认值,这应该是很常用的操作了,为此你可能会写如下的代码:

1
2
3
4
if key in some_dict:
value = some_dict[key]
else:
value = default_value

使用dict内置的get方法,可以让你的代码更简洁:

1
value = some_dict.get(key,default_value)

如果你有一堆字符串需要按照首字母分类,你可能会这样写:

1
2
3
4
5
6
7
8
9
words = ['apple','bat','bar','atom','book']
by_letter = {}

for word in words:
letter = word[0]
if letter not in by_letter:
by_letter[letter] = [word]
else:
by_letter[letter].append(word)

借助dict的setdefault方法,可以让你的代码更简洁:

1
2
3
words = ['apple','bat','bar','atom','book']
by_letter = {}

key的类型

字典的值可以是任何python的对象,而key则不行,必须是不可变类型,你可以用hash函数去检测,如果能返回值,说明它可以作为字典的key,如果报错就不行:

1
2
hash('string') # ok
hash((1,2,[2,3])) # 报错,因为list是可变的

如果一定要list作为键,可以先将它转化成元组。

1
2
3
d = {}
d[tuple([1,2,3])] = 5
d

字典高级用法

collection模块对字典也提供了很多方便的方法:

多个字典遍历

1
2
3
4
5
from collections import ChainMap
c = ChainMap(a,b)
print(c['x']) # Outputs 1 (from a)
print(c['y']) # Outputs 2 (from b)
print(c['z']) # Outputs 3 (from a)

字典合并

1
2
3
4
5
6
7
8
9
a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
merged = ChainMap(a, b)
merged['x'] # 输出 1

# 修改
a['x'] = 42
merged['x'] # 输出42

集合

集合是没有顺序关系的元素放在一起。你可以想象成是字典中,只有key,没有值。字典的创建有两种方式:

1
2
set([1,2,3,4])
{1,2,3,4}

集合支持数学层面的运算,比如交集、并集等。

1
2
3
4
5
6
7
a ={1,2,3,4,5,6,7,8}
b = {3,4,5,6,7,8}
}
a.union(b) 或者 a | b
a.intersection(b) 或者 a & b

{1,2,3}.issubset(a)

更多的运算规则可以去网上查查看。