PageRenderTime 63ms CodeModel.GetById 30ms app.highlight 13ms RepoModel.GetById 17ms app.codeStats 0ms

/Lib/collections.py

http://unladen-swallow.googlecode.com/
Python | 147 lines | 141 code | 2 blank | 4 comment | 4 complexity | e8c73288ab14216356a1e5966b0f8503 MD5 | raw file
  1__all__ = ['deque', 'defaultdict', 'namedtuple']
  2# For bootstrapping reasons, the collection ABCs are defined in _abcoll.py.
  3# They should however be considered an integral part of collections.py.
  4from _abcoll import *
  5import _abcoll
  6__all__ += _abcoll.__all__
  7
  8from _collections import deque, defaultdict
  9from operator import itemgetter as _itemgetter
 10from keyword import iskeyword as _iskeyword
 11import sys as _sys
 12
 13def namedtuple(typename, field_names, verbose=False):
 14    """Returns a new subclass of tuple with named fields.
 15
 16    >>> Point = namedtuple('Point', 'x y')
 17    >>> p = Point(11, y=22)             # instantiate with positional args or keywords
 18    >>> p[0] + p[1]                     # indexable like a plain tuple
 19    33
 20    >>> x, y = p                        # unpack like a regular tuple
 21    >>> x, y
 22    (11, 22)
 23    >>> p.x + p.y                       # fields also accessable by name
 24    33
 25    >>> d = p._asdict()                 # convert to a dictionary
 26    >>> d['x']
 27    11
 28    >>> Point(**d)                      # convert from a dictionary
 29    Point(x=11, y=22)
 30    >>> p._replace(x=100)               # _replace() is like str.replace() but targets named fields
 31    Point(x=100, y=22)
 32
 33    """
 34
 35    # Parse and validate the field names.  Validation serves two purposes,
 36    # generating informative error messages and preventing template injection attacks.
 37    if isinstance(field_names, basestring):
 38        field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas
 39    field_names = tuple(map(str, field_names))
 40    for name in (typename,) + field_names:
 41        if not all(c.isalnum() or c=='_' for c in name):
 42            raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name)
 43        if _iskeyword(name):
 44            raise ValueError('Type names and field names cannot be a keyword: %r' % name)
 45        if name[0].isdigit():
 46            raise ValueError('Type names and field names cannot start with a number: %r' % name)
 47    seen_names = set()
 48    for name in field_names:
 49        if name.startswith('_'):
 50            raise ValueError('Field names cannot start with an underscore: %r' % name)
 51        if name in seen_names:
 52            raise ValueError('Encountered duplicate field name: %r' % name)
 53        seen_names.add(name)
 54
 55    # Create and fill-in the class template
 56    numfields = len(field_names)
 57    argtxt = repr(field_names).replace("'", "")[1:-1]   # tuple repr without parens or quotes
 58    reprtxt = ', '.join('%s=%%r' % name for name in field_names)
 59    dicttxt = ', '.join('%r: t[%d]' % (name, pos) for pos, name in enumerate(field_names))
 60    template = '''class %(typename)s(tuple):
 61        '%(typename)s(%(argtxt)s)' \n
 62        __slots__ = () \n
 63        _fields = %(field_names)r \n
 64        def __new__(_cls, %(argtxt)s):
 65            return _tuple.__new__(_cls, (%(argtxt)s)) \n
 66        @classmethod
 67        def _make(cls, iterable, new=tuple.__new__, len=len):
 68            'Make a new %(typename)s object from a sequence or iterable'
 69            result = new(cls, iterable)
 70            if len(result) != %(numfields)d:
 71                raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
 72            return result \n
 73        def __repr__(self):
 74            return '%(typename)s(%(reprtxt)s)' %% self \n
 75        def _asdict(t):
 76            'Return a new dict which maps field names to their values'
 77            return {%(dicttxt)s} \n
 78        def _replace(_self, **kwds):
 79            'Return a new %(typename)s object replacing specified fields with new values'
 80            result = _self._make(map(kwds.pop, %(field_names)r, _self))
 81            if kwds:
 82                raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
 83            return result \n
 84        def __getnewargs__(self):
 85            return tuple(self) \n\n''' % locals()
 86    for i, name in enumerate(field_names):
 87        template += '        %s = _property(_itemgetter(%d))\n' % (name, i)
 88    if verbose:
 89        print template
 90
 91    # Execute the template string in a temporary namespace and
 92    # support tracing utilities by setting a value for frame.f_globals['__name__']
 93    namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename,
 94                     _property=property, _tuple=tuple)
 95    try:
 96        exec template in namespace
 97    except SyntaxError, e:
 98        raise SyntaxError(e.message + ':\n' + template)
 99    result = namespace[typename]
100
101    # For pickling to work, the __module__ variable needs to be set to the frame
102    # where the named tuple is created.  Bypass this step in enviroments where
103    # sys._getframe is not defined (Jython for example).
104    if hasattr(_sys, '_getframe'):
105        result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
106
107    return result
108
109
110
111
112
113
114if __name__ == '__main__':
115    # verify that instances can be pickled
116    from cPickle import loads, dumps
117    Point = namedtuple('Point', 'x, y', True)
118    p = Point(x=10, y=20)
119    assert p == loads(dumps(p))
120
121    # test and demonstrate ability to override methods
122    class Point(namedtuple('Point', 'x y')):
123        __slots__ = ()
124        @property
125        def hypot(self):
126            return (self.x ** 2 + self.y ** 2) ** 0.5
127        def __str__(self):
128            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)
129
130    for p in Point(3, 4), Point(14, 5/7.):
131        print p
132
133    class Point(namedtuple('Point', 'x y')):
134        'Point class with optimized _make() and _replace() without error-checking'
135        __slots__ = ()
136        _make = classmethod(tuple.__new__)
137        def _replace(self, _map=map, **kwds):
138            return self._make(_map(kwds.get, ('x', 'y'), self))
139
140    print Point(11, 22)._replace(x=100)
141
142    Point3D = namedtuple('Point3D', Point._fields + ('z',))
143    print Point3D.__doc__
144
145    import doctest
146    TestResults = namedtuple('TestResults', 'failed attempted')
147    print TestResults(*doctest.testmod())