1 | """ |
---|
2 | Tools to mess with dicts. |
---|
3 | """ |
---|
4 | |
---|
5 | from __future__ import annotations |
---|
6 | from typing import Callable, TypeVar |
---|
7 | |
---|
8 | K = TypeVar("K") |
---|
9 | V = TypeVar("V") |
---|
10 | |
---|
11 | def filter(pred: Callable[[V], bool], orig: dict[K, V]) -> dict[K, V]: |
---|
12 | """ |
---|
13 | Filter out key/value pairs whose value fails to match a predicate. |
---|
14 | """ |
---|
15 | return { |
---|
16 | k: v |
---|
17 | for (k, v) |
---|
18 | in orig.items() |
---|
19 | if pred(v) |
---|
20 | } |
---|
21 | |
---|
22 | class DictOfSets(dict): |
---|
23 | def add(self, key, value): |
---|
24 | if key in self: |
---|
25 | self[key].add(value) |
---|
26 | else: |
---|
27 | self[key] = set([value]) |
---|
28 | |
---|
29 | def update(self, otherdictofsets): |
---|
30 | for key, values in list(otherdictofsets.items()): |
---|
31 | if key in self: |
---|
32 | self[key].update(values) |
---|
33 | else: |
---|
34 | self[key] = set(values) |
---|
35 | |
---|
36 | def discard(self, key, value): |
---|
37 | if not key in self: |
---|
38 | return |
---|
39 | self[key].discard(value) |
---|
40 | if not self[key]: |
---|
41 | del self[key] |
---|
42 | |
---|
43 | class AuxValueDict(dict): |
---|
44 | """I behave like a regular dict, but each key is associated with two |
---|
45 | values: the main value, and an auxilliary one. Setting the main value |
---|
46 | (with the usual d[key]=value) clears the auxvalue. You can set both main |
---|
47 | and auxvalue at the same time, and can retrieve the values separately. |
---|
48 | |
---|
49 | The main use case is a dictionary that represents unpacked child values |
---|
50 | for a directory node, where a common pattern is to modify one or more |
---|
51 | children and then pass the dict back to a packing function. The original |
---|
52 | packed representation can be cached in the auxvalue, and the packing |
---|
53 | function can use it directly on all unmodified children. On large |
---|
54 | directories with a complex packing function, this can save considerable |
---|
55 | time.""" |
---|
56 | |
---|
57 | def __init__(self, *args, **kwargs): |
---|
58 | super(AuxValueDict, self).__init__(*args, **kwargs) |
---|
59 | self.auxilliary = {} |
---|
60 | |
---|
61 | def __setitem__(self, key, value): |
---|
62 | super(AuxValueDict, self).__setitem__(key, value) |
---|
63 | self.auxilliary[key] = None # clear the auxvalue |
---|
64 | |
---|
65 | def __delitem__(self, key): |
---|
66 | super(AuxValueDict, self).__delitem__(key) |
---|
67 | self.auxilliary.pop(key) |
---|
68 | |
---|
69 | def get_aux(self, key, default=None): |
---|
70 | """Retrieve the auxilliary value. There is no way to distinguish |
---|
71 | between an auxvalue of 'None' and a key that does not have an |
---|
72 | auxvalue, and get_aux() will not raise KeyError when called with a |
---|
73 | missing key.""" |
---|
74 | return self.auxilliary.get(key, default) |
---|
75 | |
---|
76 | def set_with_aux(self, key, value, auxilliary): |
---|
77 | """Set both the main value and the auxilliary value. There is no way |
---|
78 | to distinguish between an auxvalue of 'None' and a key that does not |
---|
79 | have an auxvalue.""" |
---|
80 | super(AuxValueDict, self).__setitem__(key, value) |
---|
81 | self.auxilliary[key] = auxilliary |
---|
82 | |
---|
83 | |
---|
84 | class _TypedKeyDict(dict): |
---|
85 | """Dictionary that enforces key type. |
---|
86 | |
---|
87 | Doesn't override everything, but probably good enough to catch most |
---|
88 | problems. |
---|
89 | |
---|
90 | Subclass and override KEY_TYPE. |
---|
91 | """ |
---|
92 | |
---|
93 | KEY_TYPE = object |
---|
94 | |
---|
95 | def __init__(self, *args, **kwargs): |
---|
96 | dict.__init__(self, *args, **kwargs) |
---|
97 | for key in self: |
---|
98 | if not isinstance(key, self.KEY_TYPE): |
---|
99 | raise TypeError("{} must be of type {}".format( |
---|
100 | repr(key), self.KEY_TYPE)) |
---|
101 | |
---|
102 | |
---|
103 | def _make_enforcing_override(K, method_name): |
---|
104 | def f(self, key, *args, **kwargs): |
---|
105 | if not isinstance(key, self.KEY_TYPE): |
---|
106 | raise TypeError("{} must be of type {}".format( |
---|
107 | repr(key), self.KEY_TYPE)) |
---|
108 | return getattr(dict, method_name)(self, key, *args, **kwargs) |
---|
109 | f.__name__ = method_name |
---|
110 | setattr(K, method_name, f) |
---|
111 | |
---|
112 | for _method_name in ["__setitem__", "__getitem__", "setdefault", "get", |
---|
113 | "__delitem__"]: |
---|
114 | _make_enforcing_override(_TypedKeyDict, _method_name) |
---|
115 | del _method_name |
---|
116 | |
---|
117 | |
---|
118 | class BytesKeyDict(_TypedKeyDict): |
---|
119 | """Keys should be bytes.""" |
---|
120 | |
---|
121 | KEY_TYPE = bytes |
---|
122 | |
---|
123 | |
---|
124 | class UnicodeKeyDict(_TypedKeyDict): |
---|
125 | """Keys should be unicode strings.""" |
---|
126 | |
---|
127 | KEY_TYPE = str |
---|