comparison env/lib/python3.9/site-packages/boltons/namedutils.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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