Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/boltons/namedutils.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """\ | |
3 The ``namedutils`` module defines two lightweight container types: | |
4 :class:`namedtuple` and :class:`namedlist`. Both are subtypes of built-in | |
5 sequence types, which are very fast and efficient. They simply add | |
6 named attribute accessors for specific indexes within themselves. | |
7 | |
8 The :class:`namedtuple` is identical to the built-in | |
9 :class:`collections.namedtuple`, with a couple of enhancements, | |
10 including a ``__repr__`` more suitable to inheritance. | |
11 | |
12 The :class:`namedlist` is the mutable counterpart to the | |
13 :class:`namedtuple`, and is much faster and lighter-weight than | |
14 full-blown :class:`object`. Consider this if you're implementing nodes | |
15 in a tree, graph, or other mutable data structure. If you want an even | |
16 skinnier approach, you'll probably have to look to C. | |
17 """ | |
18 | |
19 from __future__ import print_function | |
20 | |
21 import sys as _sys | |
22 try: | |
23 from collections import OrderedDict | |
24 except ImportError: | |
25 # backwards compatibility (2.6 has no OrderedDict) | |
26 OrderedDict = dict | |
27 from keyword import iskeyword as _iskeyword | |
28 from operator import itemgetter as _itemgetter | |
29 | |
30 try: | |
31 basestring | |
32 def exec_(code, global_env): | |
33 exec("exec code in global_env") | |
34 except NameError: | |
35 basestring = (str, bytes) # Python 3 compat | |
36 def exec_(code, global_env): | |
37 exec(code, global_env) | |
38 | |
39 __all__ = ['namedlist', 'namedtuple'] | |
40 | |
41 # Tiny templates | |
42 | |
43 _repr_tmpl = '{name}=%r' | |
44 | |
45 _imm_field_tmpl = '''\ | |
46 {name} = _property(_itemgetter({index:d}), doc='Alias for field {index:d}') | |
47 ''' | |
48 | |
49 _m_field_tmpl = '''\ | |
50 {name} = _property(_itemgetter({index:d}), _itemsetter({index:d}), doc='Alias for field {index:d}') | |
51 ''' | |
52 | |
53 ################################################################# | |
54 ### namedtuple | |
55 ################################################################# | |
56 | |
57 _namedtuple_tmpl = '''\ | |
58 class {typename}(tuple): | |
59 '{typename}({arg_list})' | |
60 | |
61 __slots__ = () | |
62 | |
63 _fields = {field_names!r} | |
64 | |
65 def __new__(_cls, {arg_list}): # TODO: tweak sig to make more extensible | |
66 'Create new instance of {typename}({arg_list})' | |
67 return _tuple.__new__(_cls, ({arg_list})) | |
68 | |
69 @classmethod | |
70 def _make(cls, iterable, new=_tuple.__new__, len=len): | |
71 'Make a new {typename} object from a sequence or iterable' | |
72 result = new(cls, iterable) | |
73 if len(result) != {num_fields:d}: | |
74 raise TypeError('Expected {num_fields:d}' | |
75 ' arguments, got %d' % len(result)) | |
76 return result | |
77 | |
78 def __repr__(self): | |
79 'Return a nicely formatted representation string' | |
80 tmpl = self.__class__.__name__ + '({repr_fmt})' | |
81 return tmpl % self | |
82 | |
83 def _asdict(self): | |
84 'Return a new OrderedDict which maps field names to their values' | |
85 return OrderedDict(zip(self._fields, self)) | |
86 | |
87 def _replace(_self, **kwds): | |
88 'Return a new {typename} object replacing field(s) with new values' | |
89 result = _self._make(map(kwds.pop, {field_names!r}, _self)) | |
90 if kwds: | |
91 raise ValueError('Got unexpected field names: %r' % kwds.keys()) | |
92 return result | |
93 | |
94 def __getnewargs__(self): | |
95 'Return self as a plain tuple. Used by copy and pickle.' | |
96 return tuple(self) | |
97 | |
98 __dict__ = _property(_asdict) | |
99 | |
100 def __getstate__(self): | |
101 'Exclude the OrderedDict from pickling' # wat | |
102 pass | |
103 | |
104 {field_defs} | |
105 ''' | |
106 | |
107 def namedtuple(typename, field_names, verbose=False, rename=False): | |
108 """Returns a new subclass of tuple with named fields. | |
109 | |
110 >>> Point = namedtuple('Point', ['x', 'y']) | |
111 >>> Point.__doc__ # docstring for the new class | |
112 'Point(x, y)' | |
113 >>> p = Point(11, y=22) # instantiate with pos args or keywords | |
114 >>> p[0] + p[1] # indexable like a plain tuple | |
115 33 | |
116 >>> x, y = p # unpack like a regular tuple | |
117 >>> x, y | |
118 (11, 22) | |
119 >>> p.x + p.y # fields also accessible by name | |
120 33 | |
121 >>> d = p._asdict() # convert to a dictionary | |
122 >>> d['x'] | |
123 11 | |
124 >>> Point(**d) # convert from a dictionary | |
125 Point(x=11, y=22) | |
126 >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields | |
127 Point(x=100, y=22) | |
128 """ | |
129 | |
130 # Validate the field names. At the user's option, either generate an error | |
131 # message or automatically replace the field name with a valid name. | |
132 if isinstance(field_names, basestring): | |
133 field_names = field_names.replace(',', ' ').split() | |
134 field_names = [str(x) for x in field_names] | |
135 if rename: | |
136 seen = set() | |
137 for index, name in enumerate(field_names): | |
138 if (not all(c.isalnum() or c == '_' for c in name) | |
139 or _iskeyword(name) | |
140 or not name | |
141 or name[0].isdigit() | |
142 or name.startswith('_') | |
143 or name in seen): | |
144 field_names[index] = '_%d' % index | |
145 seen.add(name) | |
146 for name in [typename] + field_names: | |
147 if not all(c.isalnum() or c == '_' for c in name): | |
148 raise ValueError('Type names and field names can only contain ' | |
149 'alphanumeric characters and underscores: %r' | |
150 % name) | |
151 if _iskeyword(name): | |
152 raise ValueError('Type names and field names cannot be a ' | |
153 'keyword: %r' % name) | |
154 if name[0].isdigit(): | |
155 raise ValueError('Type names and field names cannot start with ' | |
156 'a number: %r' % name) | |
157 seen = set() | |
158 for name in field_names: | |
159 if name.startswith('_') and not rename: | |
160 raise ValueError('Field names cannot start with an underscore: ' | |
161 '%r' % name) | |
162 if name in seen: | |
163 raise ValueError('Encountered duplicate field name: %r' % name) | |
164 seen.add(name) | |
165 | |
166 # Fill-in the class template | |
167 fmt_kw = {'typename': typename} | |
168 fmt_kw['field_names'] = tuple(field_names) | |
169 fmt_kw['num_fields'] = len(field_names) | |
170 fmt_kw['arg_list'] = repr(tuple(field_names)).replace("'", "")[1:-1] | |
171 fmt_kw['repr_fmt'] = ', '.join(_repr_tmpl.format(name=name) | |
172 for name in field_names) | |
173 fmt_kw['field_defs'] = '\n'.join(_imm_field_tmpl.format(index=index, name=name) | |
174 for index, name in enumerate(field_names)) | |
175 class_definition = _namedtuple_tmpl.format(**fmt_kw) | |
176 | |
177 if verbose: | |
178 print(class_definition) | |
179 | |
180 # Execute the template string in a temporary namespace and support | |
181 # tracing utilities by setting a value for frame.f_globals['__name__'] | |
182 namespace = dict(_itemgetter=_itemgetter, | |
183 __name__='namedtuple_%s' % typename, | |
184 OrderedDict=OrderedDict, | |
185 _property=property, | |
186 _tuple=tuple) | |
187 try: | |
188 exec_(class_definition, namespace) | |
189 except SyntaxError as e: | |
190 raise SyntaxError(e.message + ':\n' + class_definition) | |
191 result = namespace[typename] | |
192 | |
193 # For pickling to work, the __module__ variable needs to be set to the frame | |
194 # where the named tuple is created. Bypass this step in environments where | |
195 # sys._getframe is not defined (Jython for example) or sys._getframe is not | |
196 # defined for arguments greater than 0 (IronPython). | |
197 try: | |
198 frame = _sys._getframe(1) | |
199 result.__module__ = frame.f_globals.get('__name__', '__main__') | |
200 except (AttributeError, ValueError): | |
201 pass | |
202 | |
203 return result | |
204 | |
205 | |
206 ################################################################# | |
207 ### namedlist | |
208 ################################################################# | |
209 | |
210 _namedlist_tmpl = '''\ | |
211 class {typename}(list): | |
212 '{typename}({arg_list})' | |
213 | |
214 __slots__ = () | |
215 | |
216 _fields = {field_names!r} | |
217 | |
218 def __new__(_cls, {arg_list}): # TODO: tweak sig to make more extensible | |
219 'Create new instance of {typename}({arg_list})' | |
220 return _list.__new__(_cls, ({arg_list})) | |
221 | |
222 def __init__(self, {arg_list}): # tuple didn't need this but list does | |
223 return _list.__init__(self, ({arg_list})) | |
224 | |
225 @classmethod | |
226 def _make(cls, iterable, new=_list, len=len): | |
227 'Make a new {typename} object from a sequence or iterable' | |
228 # why did this function exist? why not just star the | |
229 # iterable like below? | |
230 result = cls(*iterable) | |
231 if len(result) != {num_fields:d}: | |
232 raise TypeError('Expected {num_fields:d} arguments,' | |
233 ' got %d' % len(result)) | |
234 return result | |
235 | |
236 def __repr__(self): | |
237 'Return a nicely formatted representation string' | |
238 tmpl = self.__class__.__name__ + '({repr_fmt})' | |
239 return tmpl % tuple(self) | |
240 | |
241 def _asdict(self): | |
242 'Return a new OrderedDict which maps field names to their values' | |
243 return OrderedDict(zip(self._fields, self)) | |
244 | |
245 def _replace(_self, **kwds): | |
246 'Return a new {typename} object replacing field(s) with new values' | |
247 result = _self._make(map(kwds.pop, {field_names!r}, _self)) | |
248 if kwds: | |
249 raise ValueError('Got unexpected field names: %r' % kwds.keys()) | |
250 return result | |
251 | |
252 def __getnewargs__(self): | |
253 'Return self as a plain list. Used by copy and pickle.' | |
254 return tuple(self) | |
255 | |
256 __dict__ = _property(_asdict) | |
257 | |
258 def __getstate__(self): | |
259 'Exclude the OrderedDict from pickling' # wat | |
260 pass | |
261 | |
262 {field_defs} | |
263 ''' | |
264 | |
265 | |
266 def namedlist(typename, field_names, verbose=False, rename=False): | |
267 """Returns a new subclass of list with named fields. | |
268 | |
269 >>> Point = namedlist('Point', ['x', 'y']) | |
270 >>> Point.__doc__ # docstring for the new class | |
271 'Point(x, y)' | |
272 >>> p = Point(11, y=22) # instantiate with pos args or keywords | |
273 >>> p[0] + p[1] # indexable like a plain list | |
274 33 | |
275 >>> x, y = p # unpack like a regular list | |
276 >>> x, y | |
277 (11, 22) | |
278 >>> p.x + p.y # fields also accessible by name | |
279 33 | |
280 >>> d = p._asdict() # convert to a dictionary | |
281 >>> d['x'] | |
282 11 | |
283 >>> Point(**d) # convert from a dictionary | |
284 Point(x=11, y=22) | |
285 >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields | |
286 Point(x=100, y=22) | |
287 """ | |
288 | |
289 # Validate the field names. At the user's option, either generate an error | |
290 # message or automatically replace the field name with a valid name. | |
291 if isinstance(field_names, basestring): | |
292 field_names = field_names.replace(',', ' ').split() | |
293 field_names = [str(x) for x in field_names] | |
294 if rename: | |
295 seen = set() | |
296 for index, name in enumerate(field_names): | |
297 if (not all(c.isalnum() or c == '_' for c in name) | |
298 or _iskeyword(name) | |
299 or not name | |
300 or name[0].isdigit() | |
301 or name.startswith('_') | |
302 or name in seen): | |
303 field_names[index] = '_%d' % index | |
304 seen.add(name) | |
305 for name in [typename] + field_names: | |
306 if not all(c.isalnum() or c == '_' for c in name): | |
307 raise ValueError('Type names and field names can only contain ' | |
308 'alphanumeric characters and underscores: %r' | |
309 % name) | |
310 if _iskeyword(name): | |
311 raise ValueError('Type names and field names cannot be a ' | |
312 'keyword: %r' % name) | |
313 if name[0].isdigit(): | |
314 raise ValueError('Type names and field names cannot start with ' | |
315 'a number: %r' % name) | |
316 seen = set() | |
317 for name in field_names: | |
318 if name.startswith('_') and not rename: | |
319 raise ValueError('Field names cannot start with an underscore: ' | |
320 '%r' % name) | |
321 if name in seen: | |
322 raise ValueError('Encountered duplicate field name: %r' % name) | |
323 seen.add(name) | |
324 | |
325 # Fill-in the class template | |
326 fmt_kw = {'typename': typename} | |
327 fmt_kw['field_names'] = tuple(field_names) | |
328 fmt_kw['num_fields'] = len(field_names) | |
329 fmt_kw['arg_list'] = repr(tuple(field_names)).replace("'", "")[1:-1] | |
330 fmt_kw['repr_fmt'] = ', '.join(_repr_tmpl.format(name=name) | |
331 for name in field_names) | |
332 fmt_kw['field_defs'] = '\n'.join(_m_field_tmpl.format(index=index, name=name) | |
333 for index, name in enumerate(field_names)) | |
334 class_definition = _namedlist_tmpl.format(**fmt_kw) | |
335 | |
336 if verbose: | |
337 print(class_definition) | |
338 | |
339 def _itemsetter(key): | |
340 def _itemsetter(obj, value): | |
341 obj[key] = value | |
342 return _itemsetter | |
343 | |
344 # Execute the template string in a temporary namespace and support | |
345 # tracing utilities by setting a value for frame.f_globals['__name__'] | |
346 namespace = dict(_itemgetter=_itemgetter, | |
347 _itemsetter=_itemsetter, | |
348 __name__='namedlist_%s' % typename, | |
349 OrderedDict=OrderedDict, | |
350 _property=property, | |
351 _list=list) | |
352 try: | |
353 exec_(class_definition, namespace) | |
354 except SyntaxError as e: | |
355 raise SyntaxError(e.message + ':\n' + class_definition) | |
356 result = namespace[typename] | |
357 | |
358 # For pickling to work, the __module__ variable needs to be set to | |
359 # the frame where the named list is created. Bypass this step in | |
360 # environments where sys._getframe is not defined (Jython for | |
361 # example) or sys._getframe is not defined for arguments greater | |
362 # than 0 (IronPython). | |
363 try: | |
364 frame = _sys._getframe(1) | |
365 result.__module__ = frame.f_globals.get('__name__', '__main__') | |
366 except (AttributeError, ValueError): | |
367 pass | |
368 | |
369 return result |