Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- __all__ = ['recordtype']
- import sys
- from textwrap import dedent
- from keyword import iskeyword
- def recordtype(typename, field_names, verbose=False, **default_kwds):
- '''Returns a new class with named fields.
- @keyword field_defaults: A mapping from (a subset of) field names to default
- values.
- @keyword default: If provided, the default value for all fields without an
- explicit default in `field_defaults`.
- >>> Point = recordtype('Point', 'x y', default=0)
- >>> Point.__doc__ # docstring for the new class
- 'Point(x, y)'
- >>> Point() # instantiate with defaults
- Point(x=0, y=0)
- >>> p = Point(11, y=22) # instantiate with positional args or keywords
- >>> p[0] + p.y # accessible by name and index
- 33
- >>> p.x = 100; p[1] =200 # modifiable by name and index
- >>> p
- Point(x=100, y=200)
- >>> x, y = p # unpack
- >>> x, y
- (100, 200)
- >>> d = p.todict() # convert to a dictionary
- >>> d['x']
- 100
- >>> Point(**d) == p # convert from a dictionary
- True
- '''
- # Parse and validate the field names. Validation serves two purposes,
- # generating informative error messages and preventing template injection attacks.
- if isinstance(field_names, basestring):
- # names separated by whitespace and/or commas
- field_names = field_names.replace(',', ' ').split()
- field_names = tuple(map(str, field_names))
- if not field_names:
- raise ValueError('Records must have at least one field')
- for name in (typename,) + field_names:
- if not min(c.isalnum() or c=='_' for c in name):
- raise ValueError('Type names and field names can only contain '
- 'alphanumeric characters and underscores: %r' % name)
- if iskeyword(name):
- raise ValueError('Type names and field names cannot be a keyword: %r'
- % name)
- if name[0].isdigit():
- raise ValueError('Type names and field names cannot start with a '
- 'number: %r' % name)
- seen_names = set()
- for name in field_names:
- if name.startswith('_'):
- raise ValueError('Field names cannot start with an underscore: %r'
- % name)
- if name in seen_names:
- raise ValueError('Encountered duplicate field name: %r' % name)
- seen_names.add(name)
- # determine the func_defaults of __init__
- field_defaults = default_kwds.pop('field_defaults', {})
- if 'default' in default_kwds:
- default = default_kwds.pop('default')
- init_defaults = tuple(field_defaults.get(f,default) for f in field_names)
- elif not field_defaults:
- init_defaults = None
- else:
- default_fields = field_names[-len(field_defaults):]
- if set(default_fields) != set(field_defaults):
- raise ValueError('Missing default parameter values')
- init_defaults = tuple(field_defaults[f] for f in default_fields)
- if default_kwds:
- raise ValueError('Invalid keyword arguments: %s' % default_kwds)
- # Create and fill-in the class template
- numfields = len(field_names)
- argtxt = ', '.join(field_names)
- reprtxt = ', '.join('%s=%%r' % f for f in field_names)
- dicttxt = ', '.join('%r: self.%s' % (f,f) for f in field_names)
- tupletxt = repr(tuple('self.%s' % f for f in field_names)).replace("'",'')
- inittxt = '; '.join('self.%s=%s' % (f,f) for f in field_names)
- itertxt = '; '.join('yield self.%s' % f for f in field_names)
- eqtxt = ' and '.join('self.%s==other.%s' % (f,f) for f in field_names)
- template = dedent('''
- class %(typename)s(object):
- '%(typename)s(%(argtxt)s)'
- __slots__ = %(field_names)r
- def __init__(self, %(argtxt)s):
- %(inittxt)s
- def __len__(self):
- return %(numfields)d
- def __iter__(self):
- %(itertxt)s
- def __getitem__(self, index):
- return getattr(self, self.__slots__[index])
- def __setitem__(self, index, value):
- return setattr(self, self.__slots__[index], value)
- def todict(self):
- 'Return a new dict which maps field names to their values'
- return {%(dicttxt)s}
- def __repr__(self):
- return '%(typename)s(%(reprtxt)s)' %% %(tupletxt)s
- def __eq__(self, other):
- return isinstance(other, self.__class__) and %(eqtxt)s
- def __ne__(self, other):
- return not self==other
- def __getstate__(self):
- return %(tupletxt)s
- def __setstate__(self, state):
- %(tupletxt)s = state
- ''') % locals()
- # Execute the template string in a temporary namespace
- namespace = {}
- try:
- exec template in namespace
- if verbose: print template
- except SyntaxError, e:
- raise SyntaxError(e.message + ':\n' + template)
- cls = namespace[typename]
- cls.__init__.im_func.func_defaults = init_defaults
- # For pickling to work, the __module__ variable needs to be set to the frame
- # where the named tuple is created. Bypass this step in enviroments where
- # sys._getframe is not defined (Jython for example).
- if hasattr(sys, '_getframe') and sys.platform != 'cli':
- cls.__module__ = sys._getframe(1).f_globals['__name__']
- return cls
- if __name__ == '__main__':
- import doctest
- TestResults = recordtype('TestResults', 'failed, attempted')
- print TestResults(*doctest.testmod())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement