文章目录
ChainMap objects
ChainMap类提供了快速连接多个映射让它们能被当作一个单元处理。比起创建新字典和运行多个update()
方法更快。
这个类可以被用作模拟嵌套作用域并且在模板中非常有用。
底层映射存储是以列表形式组织的。
# ignore no need
class ChainMap(_collections_abc.MutableMapping):
def __init__(self, *maps):
self.maps = list(maps) or [{}]
底层映射查询会查询maps的所有映射。相反,写入,更新和删除仅仅只会在第一个映射操作。
ChainMap通过引用合并底层映射,因此,如果改变对应的映射,那么这个改变也会反映到ChainMap上。
支持所有常规的字典方法。此外,有一个maps属性表示所有映射的列表,一个方法创建新的子上下文,和一个属性方法获取除第一个外的所有映射。
maps
用户可更新的映射列表。这个列表按照从头到尾的查找顺序排列。这个列表至少包含一个映射。
new_child(m=None)
返回一个新的ChainMap包含一个新的映射,后跟着当前实例的所有映射。如果指定参数m,则映射列表的第一个元素就为m。如果没指定,那么使用空字典。因此,d.new_child()
等同于ChainMap({}, *d.maps)
。这个方法用于创建子上下文,这样更新操作就不会影响父映射。
parents
属性方法返回一个新的ChainMap包含除第一个映射外的所有映射。在跳过第一个映射搜索方面非常有用。使用实例和嵌套作用域的nonlocal
关键字相似(闭包),同时还有super()
函数。引用d.parents
等同于ChainMap(*d.maps[1:])
。
注意:迭代ChainMap的顺序与maps列表相反。
参考:
- The MultiContext class in the Enthought CodeTools package has options to support writing to any mapping in the chain.
- Django’s Context class for templating is a read-only chain of mappings. It also features pushing and popping of contexts similar to the
new_child()
method and theparents
property.- The Nested Contexts recipe has options to control whether writes and other mutations apply only to the first mapping or to any mapping in the chain.
- A greatly simplified read-only version of Chainmap.
ChainMap Examples and Recipes
模拟Python内部查询链
python内部查询链是:局部->全局->内置
import builtins
from collections import ChainMap
pylookup = ChianMap(locals(), globals(), vars(builtins))
让用户指定的命令行参数优先于环境变量,而环境变量又优先于默认值
import os, argparse
defaults = {'color': 'red', 'user': 'guest'}
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = {k: v for k, v in vars(namespace).items() if v is not None}
combined = ChainMap(command_line_args, os.environ, defaults)
print(combined['color'])
print(combined['user'])
模拟嵌套上下文
c = ChainMap() # Create root context
d = c.new_child() # Create nested child context
e = c.new_child() # Child of c, independent from d
e.maps[0] # Current context dictionary -- like Python's locals()
e.maps[-1] # Root context -- like Python's globals()
e.parents # Enclosing context chain -- like Python's nonlocals
d['x'] = 1 # Set value in current context
d['x'] # Get first key in the chain of contexts
del d['x'] # Delete from current context
list(d) # All nested values
k in d # Check all nested values
len(d) # Number of nested values
d.items() # All nested items
dict(d) # Flatten into a regular dictionary
ChainMap只能更新(写入和删除)第一个映射而查询可以查询整个链中所有映射。如果需要写入和删除其他映射,则需要扩展:
class DeepChainMap(ChainMap):
'Variant of ChainMap that allows direct updates to inner scopes'
def __setitem__(self, key, value):
for mapping in self.maps:
if key in mapping:
mapping[key] = value
return
self.maps[0][key] = value
def __delitem__(self, key):
for mapping in self.maps:
if key in mapping:
del mapping[key]
return
raise KeyError(key)
>>> d = DeepChainMap({'zebra': 'black'}, {'elephant': 'blue'}, {'lion': 'yellow'})
>>> d['lion'] = 'orange' # update an existing key two levels down
>>> d['snake'] = 'red' # new keys get added to the topmost dict
>>> del d['elephant'] # remove an existing key one level down
>>> d # display result
DeepChainMap({'zebra': 'black', 'snake': 'red'}, {}, {'lion': 'orange'})
Counter objects
counter工具提供方便和快速统筹。例如:
>>> # Tally occurrences of words in a list
>>> cnt = Counter()
>>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
... cnt[word] += 1
>>> cnt
Counter({'blue': 3, 'red': 2, 'green': 1})
>>> # Find the ten most common words in Hamlet
>>> import re
>>> words = re.findall(r'\w+', open('hamlet.txt').read().lower())
>>> Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
('you', 554), ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
class collections.Counter([iteralble-ormapping])
Counter是一个字典子类用于给可哈希对象计数。计数值允许为负数、零和正数。类似其他语言中的bag和multisets。
有以下初始化的方式:
c = Counter() # a new, empty counter
c = Counter('gallahad') # a new counter from an iterable
c = Counter({'red': 4, 'blue': 2}) # a new counter from a mapping
c = Counter(cats=4, dogs=8) # a new counter from keyword args
Counter对象用于字典的接口,除了当键不命中时返回零而不是抛出KeyError
异常。
c = Counter(['eggs', 'ham'])
c['bacon'] # count of a missing element is zero
0
设置计数为0并不会移除这个元素,需要使用del
完全移除它:
c['sausage'] = 0 # counter entry with a zero count
del c['sausage'] # del actually removes the entry
除了所有字典方法外也支持以下三个方法:
elements()
返回一个迭代器遍历元素。计数少于1的会被忽略。元素顺序与初始化顺序相同。
c = Counter(a=4, b=2, c=0, d=-2)
sorted(c.elements())
['a', 'a', 'a', 'a', 'b', 'b']
most_common[n])
返回计数值最大的前n个元素,如果不提供n或n为None则返回所有元素。元素顺序与初始化顺序相同。
Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
subtract([iterable-or-mapping])
把当前counter对象减去迭代器或映射或Counter对象参数中的元素个数,然后更新到当前counter对象。保留计数为零或负数。
c = Counter(a=4, b=2, c=0, d=-2)
d = Counter(a=1, b=2, c=3, d=4)
c.subtract(d)
c
Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
通用字典方法都可用除了以下两个不同:
fromkeys(iterable)
该类没有实现此方法。
update([iterable-or-mapping])
更新数量而不是替代原来的值。iterable应该时元素序列(如"abc", ["a", "b"]
)而不是(key, value)
键值对的序列。
也提供一些数学操作。相加是把两者相同的键的计数相加(不存在的键计数为0);相减是把两者相同的键相减,结果只保留计数为正值的元素;相交是取想着相同键计数值的最小值;而相与是取最大值。
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d # add two counters together: c[x] + d[x]
Counter({'a': 4, 'b': 3})
c - d # subtract (keeping only positive counts)
Counter({'a': 2})
c & d # intersection: min(c[x], d[x])
Counter({'a': 1, 'b': 1})
c | d # union: max(c[x], d[x])
Counter({'a': 3, 'b': 2})
同时支持一元操作,实际上是用一个空Counter对象做二元操作:
c = Counter(a=2, b=-4)
+c # 实际上是Counter() + c
Counter({'a': 2})
-c
Counter({'b': 4})
参考:
Bag class in Smalltalk.
Wikipedia entry for Multisets.
C++ multisets tutorial with examples.
For mathematical operations on multisets and their use cases, see Knuth, Donald. The Art of Computer Programming Volume II, Section 4.6.3, Exercise 19.
To enumerate all distinct multisets of a given size over a given set of elements, see
itertools.combinations_with_replacement()
:map(Counter, combinations_with_replacement('ABC', 2)) # --> AA AB AC BB BC CC
deque objects
class collections.deque([iterable[, maxlen]])
从迭代器左到右遍历元素初始化新的双向队列。
deque是栈和队列的概括。线程安全,并且双端插入和删除有O(1)的时间复杂度。
尽管list能提供相似操作,他们针对固定长度的操作做了优化并且从头部插入和删除需要O(n)的时间复杂度。
如果不指定maxlen或为None,deque长度无限制。一旦队列满了,此时向某一端插入元素,对端方向会丢弃一个元素。固定长度的队列提供类似Linux下tail
的功能。对于追踪事务和池中感兴趣的最近活跃的数据非常有用。
支持以下方法,只列出需要注意的,其他的和queue接口相同。
appendleft()
从左端添加数据。
extendleft(iterable)
从左端添加数据。
popleft()
从左端删除数据。
rotate(n=1)
n为正则是右旋,n为负为左旋。
右旋n步:把右边n个元素移到左边。
左旋n步:把左边n个元素移到右边。
使用例子:
>>> from collections import deque
>>> d = deque('ghi') # make a new deque with three items
>>> for elem in d: # iterate over the deque's elements
... print(elem.upper())
G
H
I
>>> d.append('j') # add a new entry to the right side
>>> d.appendleft('f') # add a new entry to the left side
>>> d # show the representation of the deque
deque(['f', 'g', 'h', 'i', 'j'])
>>> d.pop() # return and remove the rightmost item
'j'
>>> d.popleft() # return and remove the leftmost item
'f'
>>> list(d) # list the contents of the deque
['g', 'h', 'i']
>>> d[0] # peek at leftmost item
'g'
>>> d[-1] # peek at rightmost item
'i'
>>> list(reversed(d)) # list the contents of a deque in reverse
['i', 'h', 'g']
>>> 'h' in d # search the deque
True
>>> d.extend('jkl') # add multiple elements at once
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> d.rotate(1) # right rotation
>>> d
deque(['l', 'g', 'h', 'i', 'j', 'k'])
>>> d.rotate(-1) # left rotation
>>> d
deque(['g', 'h', 'i', 'j', 'k', 'l'])
>>> deque(reversed(d)) # make a new deque in reverse order
deque(['l', 'k', 'j', 'i', 'h', 'g'])
>>> d.clear() # empty the deque
>>> d.pop() # cannot pop from an empty deque
Traceback (most recent call last):
File "<pyshell#6>", line 1, in -toplevel-
d.pop()
IndexError: pop from an empty deque
>>> d.extendleft('abc') # extendleft() reverses the input order
>>> d
deque(['c', 'b', 'a'])
deque Recipes
类似Linux下tail的功能
def tail(filename, n=10):
'Return the last n lines of a file'
with open(filename) as f:
return deque(f, n)
维护最近添加的元素
def moving_average(iterable, n=3):
# moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
# http://en.wikipedia.org/wiki/Moving_average
it = iter(iterable)
d = deque(itertools.islice(it, n-1))
d.appendleft(0)
s = sum(d)
for elem in it:
s += elem - d.popleft()
d.append(elem)
yield s / n
实现轮询调用
从活跃迭代器的第一个位置获取元素,如果迭代器耗尽则移除。否则进行队列旋转。
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
iterators = deque(map(iter, iterables))
while iterators:
try:
while True:
yield next(iterators[0])
iterators.rotate(-1)
except StopIteration:
# Remove an exhausted iterator.
iterators.popleft()
实现deque切片和删除元素
删除d[n]:
def delete_nth(d, n):
d.rotate(-n)
d.popleft()
d.rotate(n)
defaultdict objects
class collections.defaultdict([default_factory[,...]])
返回一个新的类字典对象。defaultdict
是dict
的子类。它重载了一个方法和添加一个可写实例变量。其他的功能和dict1
相同。
除了以下方法,其他都和dict
相同:
__missing__(key)
如果default_factory
属性为None,如果key不存在字典中将抛出KeyError
异常。
如果default_factory
非None,将被调用作为这个key的默认值,且这个值会插入到字典中,键为key,然后返回。
注意__missing__()
只会由__getitem__()
调用。这表名get()
获取不存在的键时会返回None而不是使用default_factory
。
defaultdict Examples
根据key增长列表
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
d[k].append(v)
sorted(d.items())
计数器
s = 'mississippi'
d = defaultdict(int)
for k in s:
d[k] += 1
sorted(d.items())
更通用的常量工厂
def constant_factory(value):
return lambda: value
d = defaultdict(constant_factory('<missing>'))
d.update(name='John', action='ran')
'%(name)s %(action)s to %(object)s' % d
字典集合
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
d[k].add(v)
sorted(d.items())
namedtuple() Factory Function for Tuples with Named Fields
命名元组为元组每一个位置赋予含义,使得可读性更好和自带文档。除了通过位置获取字段,还可以通过name获取字段。
collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
返回一个名为typename的新元组子类。
field_name参数可以为字符串列表["x", "y"]
,或单个字符串,以空格或逗号分隔,"x y"
或"x, y"
。
defaults参数可以为None也可以为默认值的迭代器。默认值从右边开始匹配。例如,字段名为["x", "y", "z"]
,而defaults参数为(1, 2)
,那么字段y默认值为1,z的默认值为2。
命名元组没有实例字典,这使得它相对普通元组更轻量和需要更少内存。
Examples
>>> # Basic example
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22) # instantiate with positional or keyword arguments
>>> p[0] + p[1] # indexable like the plain tuple (11, 22)
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> p # readable __repr__ with a name=value style
Point(x=11, y=22)
命名元组对于分配字段给由csv和sqlite3模块返回的结果元组非常有用:
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print(emp.name, emp.title)
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print(emp.name, emp.title)
Methods and Attribute
除了继承元素的方法以外,命名元组还支持三个额外方法和两个属性。
Methods
classmethod somenamedtuple._make(iterable)
从已存在的序列或迭代器创建一个新的实例的类方法。
>>> t = [11, 22]
>>> Point._make(t)
Point(x=11, y=22)
somenamedtuple._asdict()
转换为字典,这个字典是有序的。
>>> p = Point(x=11, y=22)
>>> p._asdict()
{'x': 11, 'y': 22}
somenamedtuple._replace(**kwargs)
返回一个新实例,原字段的值被替换成指定的新值。
>>> p = Point(x=11, y=22)
>>> p._replace(x=33)
Point(x=33, y=22)
>>> for partnum, record in inventory.items():
... inventory[partnum] = record._replace(price=newprices[partnum], timestamp=time.now())
Attributes
somenamedtuple._fields
返回所有的字段名。
>>> p._fields # view the field names
('x', 'y')
>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)
somenamedtyple._field_defaults
映射字段和默认值的字段。
>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._field_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)
Others
把字典转换成命令元组:
d = {'x': 11, 'y': 22}
Point(**d)
通过继承增加功能:
>>> class Point(namedtuple('Point', ['x', 'y'])):
... __slots__ = ()
... @property
... def hypot(self):
... return (self.x ** 2 + self.y ** 2) ** 0.5
... def __str__(self):
... return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
>>> for p in Point(3, 4), Point(14, 5/7):
... print(p)
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
__slots__
为空元组,保证不创建实例字典,节省内存。
但子类化不能用于增加、存储字段。如果需要应该重新创建一个命名元组:
Point3D = namedtuple('Point3D', Point._fields + ('z',))
添加文档通过直接复制给__doc__
字段:
Book = namedtuple('Book', ['id', 'title', 'authors'])
Book.__doc__ += ': Hardcover book in active collection'
Book.id.__doc__ = '13-digit ISBN'
Book.title.__doc__ = 'Title of first printing'
Book.authors.__doc__ = 'List of authors sorted by last name'
参考:
See
typing.NamedTuple
for a way to add type hints for named tuples. It also provides an elegant notation using theclass
keyword:class Component(NamedTuple): part_number: int weight: float description: Optional[str] = None
See
types.SimpleNamespace()
for a mutable namespace based on an underlying dictionary instead of a tuple.The
dataclasses
module provides a decorator and functions for automatically adding generated special methods to user-defined classes.
OrderedDict objects
有序字典根据插入数据排序。Python3.7及之后内建字典默认为有序。
但仍保留一些不同:
- 普通字典擅长映射操作。追踪插入顺序是次要的。
OrderedDict
擅长重新排序操作。空间效率,迭代速度和更新操作性能都是次要的。- 算法上,
OrderedDict
比dict
更好处理经常重排序操作。这非常适合追踪最近获取(例如LRU cache)。 OrderedDict
判断相同操作是检查匹配顺序。OrderedDict
的popitem()
方法有不同的签名。接收一个可选参数指定哪个字段需要弹出。OrderedDict
的move_to_end()
方法高效地重定位一个元素到尾端。- 直到Python3.8之前,
dict
缺少__reversed__()
方法。
class collections.OrderedDict([items])
返回一个有序字典实例。
方法:
popitem(last=True)
如果last参数为True,则LIFO,反之FIFO。
move_to_end(key, last=True)
把已存在的key移到有序字典的尾端。如果last参数为True,移到右端(尾端),反之移到左端(开始处)。如果key不存在,抛出KeyError
异常。
>>> d = OrderedDict.fromkeys('abcde')
>>> d.move_to_end('b')
>>> ''.join(d.keys())
'acdeb'
>>> d.move_to_end('b', last=False)
>>> ''.join(d.keys())
'bacde'
OrderedDict
对象之间判断相等对顺序敏感,但OrderedDict
和普通字典之间判断相等不对顺序敏感。
OrderedDict Examples and Receipes
最后更新的移动到最后面:
class LastUpdatedOrderedDict(OrderedDict):
'Store items in the order the keys were last added'
def __setitem__(self, key, value):
super().__setitem__(key, value)
self.move_to_end(key)
实现functools.lru_cache()
:
class LRU(OrderedDict):
'Limit size, evicting the least recently looked-up key when full'
def __init__(self, maxsize=128, /, *args, **kwds):
self.maxsize = maxsize
super().__init__(*args, **kwds)
def __getitem__(self, key):
value = super().__getitem__(key)
self.move_to_end(key)
return value
def __setitem__(self, key, value):
if key in self:
self.move_to_end(key)
super().__setitem__(key, value)
if len(self) > self.maxsize:
oldest = next(iter(self))
del self[oldest]
UserDict
由于内建数据类型都是由C实现的,且这些类型会忽略大部分重写方法,因此如果需要继承这些数据类型扩展功能不应该继承它们,而是继承UserDict,UserList,UserString
。
UserDict 类充当字典对象的包装器。对此类的需求已部分被直接从 dict 子类化的能力所取代。
如下例子:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from collections import UserDict
class MyDict1(dict):
def __getitem__(self, item):
return 1
class MyDict2(UserDict):
def __getitem__(self, item):
return 2
dict1 = MyDict1(age=10)
dict2 = MyDict2(age=10)
print(dict1, dict2)
dict3 = {}
dict4 = {}
dict3.update(dict1)
dict4.update(dict2)
print(dict3, dict4)
# 输出如下:
{'age': 10} {'age': 10}
{'age': 10} {'age': 2}
相同的内容,经过update()
之后结果却不同。
UserList
同UserDict
UserString
同UserDict