Author: fw Date: 2010-05-07 18:50:29 +0000 (Fri, 07 May 2010) New Revision: 14623 Added: lib/python/xcollections.py Log: lib/python/xcollections.py: backport of collections.namedtuple Added: lib/python/xcollections.py ==================================================================--- lib/python/xcollections.py (rev 0) +++ lib/python/xcollections.py 2010-05-07 18:50:29 UTC (rev 14623) @@ -0,0 +1,93 @@ +# Lifted from python2.6-minimal 2.6.5-1. +# See /usr/share/doc/python2.6/copyright for copyright information. +# +# This version has been modified, unneeded functions have been removed. + +import sys as _sys +from keyword import iskeyword as _iskeyword +from operator import itemgetter as _itemgetter + +def namedtuple(typename, field_names, verbose=False): + """Returns a new subclass of tuple with named fields. + + >>> Point = namedtuple(''Point'', ''x y'') + >>> Point.__doc__ # docstring for the new class + ''Point(x, y)'' + >>> p = Point(11, y=22) # instantiate with positional args or keywords + >>> p[0] + p[1] # indexable like a plain tuple + 33 + >>> x, y = p # unpack like a regular tuple + >>> x, y + (11, 22) + >>> p.x + p.y # fields also accessable by name + 33 + >>> d = p._asdict() # convert to a dictionary + >>> d[''x''] + 11 + >>> Point(**d) # convert from a dictionary + Point(x=11, y=22) + >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields + Point(x=100, y=22) + + """ + + # Parse and validate the field names. Validation serves two purposes, + # generating informative error messages and preventing template injection attacks. + if isinstance(field_names, basestring): + field_names = field_names.replace('','', '' '').split() # names separated by whitespace and/or commas + field_names = tuple(map(str, field_names)) + for name in (typename,) + field_names: + if not all(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) + + # Create and fill-in the class template + numfields = len(field_names) + argtxt = repr(field_names).replace("''", "")[1:-1] # tuple repr without parens or quotes + reprtxt = '', ''.join(''%s=%%r'' % name for name in field_names) + dicttxt = '', ''.join(''%r: t[%d]'' % (name, pos) for pos, name in enumerate(field_names)) + template = ''''''class %(typename)s(tuple): + ''%(typename)s(%(argtxt)s)'' \n + __slots__ = () \n + _fields = %(field_names)r \n + def __new__(_cls, %(argtxt)s): + return _tuple.__new__(_cls, (%(argtxt)s)) \n + def __repr__(self): + return ''%(typename)s(%(reprtxt)s)'' %% self \n + def _asdict(t): + ''Return a new dict which maps field names to their values'' + return {%(dicttxt)s} \n + def __getnewargs__(self): + return tuple(self) \n\n'''''' % locals() + for i, name in enumerate(field_names): + template += '' %s = _property(_itemgetter(%d))\n'' % (name, i) + if verbose: + print template + + # Execute the template string in a temporary namespace and + # support tracing utilities by setting a value for frame.f_globals[''__name__''] + namespace = dict(_itemgetter=_itemgetter, __name__=''namedtuple_%s'' % typename, + _property=property, _tuple=tuple) + try: + exec template in namespace + except SyntaxError, e: + raise SyntaxError(e.message + '':\n'' + template) + result = namespace[typename] + + # 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''): + result.__module__ = _sys._getframe(1).f_globals.get(''__name__'', ''__main__'') + + return result