collections 模块用得很多,因为它提供了一些非常高效的容器数据类型。掌握这些数据类型能显著提高编码的效率。这篇文章会对 Python 2 和 Python 3 共有的 5 种数据类型加以总结,至于 Python3 中特有的另外 4 种数据类型会出现在后续的文章中.
collections 提供的五种数据类型分别是:
数据类型 | 初始化参数 |
---|---|
namedtuple() | (typename, field_names[, verbose=False][, rename=False]) |
deque | ([iterable[, maxlen]]) |
Counter | ([iterable-or-mapping]) |
OrderedDict | [items] |
defaultdict | ([default_factory[, …]]) |
注意到上表中的 namedtuple() 带有括号,说明其是一个函数,而剩余的四种数据类型都是类。函数可以返回一个返回值,而类可以实例化一个对象。
namedtuple() 返回一个类
namedtuple返回的是一个类(class), 类可以用来实例化一个对象。例如:
from collections import *
Person = namedtuple(\'Person\', [\'age\', \'sex\'])
上面的代码其实创建了一个类,这个类的类名叫做 \’Person\’ , 这个类有两个属性,分别是 \’age\’ 和 \’sex\’ . 我们可以用这个类创建实例。例如
person = Person(10, \'male\')
上面这段代码创建了一个 age=10, sex=male 的 Person 的对象 person.
nametuple() 创建named tuple
namedutple() 有四个参数,第一个参数 typename 是返回的类的名称,类的名称必须要是合法的变量名。第二个参数是属性名称,在上面我们已经看到了可以用list列出所有的属性名。事实上,我们还可以使用一个字符串来列举所哟逇属性名,这时需要使用空格或者逗号作为属性名之间的分隔符。例如:
Person = namedtuple(\'Person\', \'age sex\')
Person = namedtuple(\'Person\', \'age,sex\')
第三个参数保证其默认值即可。
第四个参数默认为False,如果为True,在设置属性名称的时候可以设置成重复,但会被自动重命名为下划线与数字的组合. 例如:
Person = namedtuple(\'Person\', \'age,age,height,age\')
p = Person(age=10,_1=\'male\',height=1.80\',_3=\'32\')
namedtuple 和 tuple的异同
顾名思义,namedtuple 是带有名字的tuple. 我们创建了一个tuple之后,要访问这个tuple的元素只能利用这个tuple的索引。比如:
t = (1,2,3)
x = t[1]
y = t[0]
z = t[2]
但是如果创建的是一个namedtuple, 既可以使用索引访问namedtuple的元素,也可以用属性名访问。例如:
Person = namedtuple(\'Person\',\'age, sex\')
p = Person(15, \'male\')
age0 = p.age
age1 = p[0]
sex0 = p.sex
sex1 = p[1]
namedtuple 类中的几种方法:
from collections import namedtuple
Person = namedtuple(\'Person\', \'height weight age sex\')
p = Person(177, 129, 22, \'male\')
t = [180, 130, 25, \'male\']
nt = Person._make(t)
pr = p._replace(age = 21)
pd = p._asdict()
print(nt)
print(pr)
print(pd)
>>>
Person(height=180, weight=130, age=25, sex=\'male\')
Person(height=177, weight=129, age=21, sex=\'male\')
OrderedDict([(\'height\',177), (\'weight\',129), (\'age\',22), (\'sex\', \'male\')])
Point = namedtuple(\'Point\',\'x y\') ## 坐标为x,y的一个点
Color = namedtuple(\'Color\', \'r g b\')
现在要创建一个又坐标和颜色组成的像素点, 可以这样:
Pixel = namedtuple(\'Pixel\', Point._fields + Color._fields)
当然也能直接创建:
Pixel = namedtuple(\'Pixel\', \'x y r g b\')
这两种方式没有什么本质区别,但是第一种方法更加的结构化。
deque 就是一个双端链表, 如下图:
deque.gif
d0 = deque() # 空双端链表
d1 = deque(\'abcde\') # 有5个元素的双端链表
d2 = deque([1,1,2,3,5,8,11], 20) # 有7个元素的双端链表,并且最大长度不能超过20
注意到成员方法reverse()是在原位置做翻转,返回的是None. 然而函数reversed(iterable) 会返回一个新的iterator, 不会影响其参数iterable. 例如
d = deque(\'abcd\')
d.reverse()
上面的d此时已经变成了deque(\’dcba\’), 然而下面的代码给出了另外一种情况
d = deque(\'abcd\')
rd = reversed(d)
lrd = list(rd)
d 依然保持不变,rd 是一个iterator, lrd 则是 [\’d\’, \’c\’,\’ b\’, \’a\’]
s = deque(\'abcd\')
s.append(\'e\') # 入栈
s.append(\'f\') # 入栈
s.pop() # 出栈
q = deque(\'xyz\')
q.append(\'a\') # 入队
q.append(\'b\') # 入队
q.popleft() # 出队
Counter 是一个计数器,当我们有计数需求的时候,使用它十分方便。例如我们需要统计一个字符串每个字符出现的次数,直接用Counter就可以帮我们搞定了。Counter 是 dict 的一个子类,因此dict 的方法可以直接用到 Counter 上来。
c0 = Counter()
c1 = Counter(\'abaabcd\')
print(c0)
print(c1)
>>>
Counter()
Counter({\'a\': 5, \'d\': 3, \'f\': 3, \'b\': 2, \'e\': 1, \'c\': 1})
因为Counter本身是一个dict, 所以我们可以随心所欲往Counter里面添加键值对.
c1[\'k\'] = 5
当然我们在初始化的时候也可以用dict去初始化一个Counter
cnt = Counter({\'apple\':5, \'peach\':10})
print(cnt)
>>>
Counter({\'apple\': 5, \'peach\': 10})
## elements() 返回的是一个iterator. 每个元素是Counter里面的key,每个元素出现的次数是key对应的值
cnt1 = Counter(\'abcdeaaabcde\')
list(cnt1.elements())
>>>
[\'b\', \'b\', \'e\', \'e\', \'c\', \'c\', \'d\', \'d\', \'a\', \'a\', \'a\', \'a\']
## most_common([n]) 返回一个list, 这个list的元素是一个tuple。这个tuple包含两个值,第一个值是key, 第二个值是key出现的次数。
cnt1.most_common()
cnt1.most_common(3)
>>>
[(\'a\', 4), (\'b\', 2), (\'e\', 2), (\'c\', 2), (\'d\', 2)]
[(\'a\', 4), (\'b\', 2), (\'e\', 2)]
## subtract(iterable-or-mapping) 会先统计iterable or mapping, 然后已有的Counter的统计值会减去相应的值得到新的值。
s = \'fashifdap\'
cnt2 = Counter(s)
print(cnt2)
cnt1.subtract(s)
print(cnt1)
>>>
Counter({\'a\': 2, \'d\': 1, \'f\': 2, \'h\': 1, \'i\': 1, \'p\': 1, \'s\': 1})
Counter({\'a\': 2, \'b\': 2, \'c\': 2, \'d\': 1, \'e\': 2, \'f\': -2, \'h\': -1, \'i\': -1, \'p\': -1, \'s\': -1})
## update(iterable or mapping) 会在已有的Counter的基础上加上iterable 或者 mapping的统计值
cnt1.update(\'xxxxxxx\')
cnt1.update({\'s\': -5}
print(cnt1)
>>>
Counter({\'a\': 2, \'b\': 2, \'c\': 2, \'d\': 1, \'e\': 2, \'f\': -2, \'h\': -1, \'i\': -1, \'p\': -1, \'s\': -6, \'x\': 7})
from collections import Counter
cnt1 = Counter(\'aaaabcjjjjjjjj\')
cnt2 = Counter({\'a\': -2, \'b\':3, \'k\': 9, \'j\': -7})
print(\"cnt1: \", cnt1)
print(\"cnt2: \", cnt2)
print(\"cnt1 + cnt2: \", cnt1 + cnt2)
print(\"cnt1 - cnt2: \", cnt1 - cnt2)
print(\"cnt1 & cnt2: \", cnt1 & cnt2)
print(\"cnt1 | cnt2: \", cnt1 | cnt2)
>>>
cnt1: Counter({\'j\': 8, \'a\': 4, \'c\': 1, \'b\': 1})
cnt2: Counter({\'k\': 9, \'b\': 3, \'a\': -2, \'j\': -7})
cnt1 + cnt2: Counter({\'k\': 9, \'b\': 4, \'a\': 2, \'j\': 1, \'c\': 1})
cnt1 - cnt2: Counter({\'j\': 15, \'a\': 6, \'c\': 1})
cnt1 & cnt2: Counter({\'b\': 1})
cnt1 | cnt2: Counter({\'k\': 9, \'j\': 8, \'a\': 4, \'b\': 3, \'c\': 1})
OrderedDict 和 dict 的不同之处在于:dict 里面的元素是无序的,而OrderedDict里面的元素是有序的。它的有序之处体现在:先插入的元素排在前面,后插入的元素排在后面。例如:
from collections import *
od = OrderedDict()
d = {}
fruits = [\'apple\', \'peach\', \'pear\', \'dragon\', \'blueberry\', \'orange\']
for f in fruits:
od[f] = 10
d[f] = 10
print(od)
print(d)
>>>
OrderedDict([(\'apple\', 10), (\'peach\', 10), (\'pear\', 10), (\'dragon\', 10), (\'blueberry\', 10), (\'orange\', 10)])
{\'apple\': 10, \'peach\': 10, \'blueberry\': 10, \'pear\': 10, \'orange\': 10, \'dragon\': 10}
我们看到od里面的元素按照我们的插入顺序排列,而d中的元素则无序排列
OrderedDict有一个成员方法 popitem. 如果last为True, 则从尾部pop出一个元素,这也是默认的。如果last为False, 则从首部pop出一个元素。例如:
t = od.popitem()
f = od.popitem(last=False)
print(t)
print(f)
>>>
(\'orange\', 10)
(\'apple\', 10)
我们知道LRU要求我们最先访问容器中最后被插入的元素,且该元素在容器中不能重复。基于这个特点,我们可以用OrderedDict实现LRU如下:
class LRU(OrderedDict):
def __setitem__(self, key, value):
if key in self:
del self[key]
OrderedDict.__setitem__(self, key, value)
else:
OrderedDict.__setitem__(self, key, value)
在使用dict的时候,如果没有某一键值对,那么去访问这个键是会报错的。比如说:
d = {\'apple\': 5, \'peach\': 10}
如果去访问d[\’dragon\’]是会报错的,因为dragon不存在与d中。defaultdict就是为了解决这个问题诞生的。例如:
dd = defaultdict(int)
print(dd[\'dragon\'])
>>>
0
上面的代码告诉我们如果某一个键值对在defaultdict中不存在,如果去访问会默认返回一个int。并且会把这个键值对加入到defaultdict中去。当然如我们把int改成其他任何类型也是可以的,比如说list, tuple 等等等等。例如:
dd = defaultdict(list)
dd[\'dragon\'] = 10
print(dd[\'apple\'])
print(dd)
>>>
[] ## 返回一个默认的list
defaultdict(, {\'dragon\': 10, \'apple\': []}) ## {\'apple\': []} 这个键值对被加入到了dd中去
如果defaultdict 不设置参数,那么和dict一样,访问不存在的键值对会报错。例如:
dd = defaultdict()
print(dd[\'apple\'])
>>>
KeyError: \'apple\' ## 因为\'apple\'是不存在的键值,并且没有指定默认返回值,故报错
上一篇:用Python爬取妹子图——基于BS4+多线程的处理
下一篇:Python 装饰器