__all__ = ['deque', 'defaultdict', 'namedtuple']

# For bootstrapping reasons, the collection ABCs are defined in _abcoll.py.

# They should however be considered an integral part of collections.py.

from _abcoll import *

import _abcoll

__all__ += _abcoll.__all__



from _collections import deque, defaultdict

from operator import itemgetter as _itemgetter

from keyword import iskeyword as _iskeyword

import sys as _sys



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

        @classmethod

        def _make(cls, iterable, new=tuple.__new__, len=len):

            'Make a new %(typename)s object from a sequence or iterable'

            result = new(cls, iterable)

            if len(result) != %(numfields)d:

                raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))

            return result \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 _replace(_self, **kwds):

            'Return a new %(typename)s object replacing specified fields with new values'

            result = _self._make(map(kwds.pop, %(field_names)r, _self))

            if kwds:

                raise ValueError('Got unexpected field names: %%r' %% kwds.keys())

            return result \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













if __name__ == '__main__':

    # verify that instances can be pickled

    from cPickle import loads, dumps

    Point = namedtuple('Point', 'x, y', True)

    p = Point(x=10, y=20)

    assert p == loads(dumps(p))



    # test and demonstrate ability to override methods

    class Point(namedtuple('Point', 'x y')):

        __slots__ = ()

        @property

        def hypot(self):

            return (self.x ** 2 + self.y ** 2) ** 0.5

        def __str__(self):

            return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)



    for p in Point(3, 4), Point(14, 5/7.):

        print p



    class Point(namedtuple('Point', 'x y')):

        'Point class with optimized _make() and _replace() without error-checking'

        __slots__ = ()

        _make = classmethod(tuple.__new__)

        def _replace(self, _map=map, **kwds):

            return self._make(_map(kwds.get, ('x', 'y'), self))



    print Point(11, 22)._replace(x=100)



    Point3D = namedtuple('Point3D', Point._fields + ('z',))

    print Point3D.__doc__



    import doctest

    TestResults = namedtuple('TestResults', 'failed attempted')

    print TestResults(*doctest.testmod())

