Add __asdict__() to NamedTuple and refine the docs.

Add maxlen support to deque() and fixup docs.
Partially fix __reduce__().  The None as a third arg was no longer supported.
Still needs work on __reduce__() to handle recursive inputs.
This commit is contained in:
Raymond Hettinger 2007-10-05 02:47:07 +00:00
parent c9b7163da5
commit a7fc4b13e0
6 changed files with 228 additions and 162 deletions

View File

@ -34,7 +34,7 @@ ordered dictionaries.
---------------------- ----------------------
.. class:: deque([iterable]) .. class:: deque([iterable[, maxlen]])
Returns a new deque object initialized left-to-right (using :meth:`append`) with Returns a new deque object initialized left-to-right (using :meth:`append`) with
data from *iterable*. If *iterable* is not specified, the new deque is empty. data from *iterable*. If *iterable* is not specified, the new deque is empty.
@ -51,6 +51,17 @@ ordered dictionaries.
.. versionadded:: 2.4 .. versionadded:: 2.4
If *maxlen* is not specified or is *-1*, deques may grow to an
arbitrary length. Otherwise, the deque is bounded to the specified maximum
length. Once a bounded length deque is full, when new items are added, a
corresponding number of items are discarded from the opposite end. Bounded
length deques provide functionality similar to the ``tail`` filter in
Unix. They are also useful for tracking transactions and other pools of data
where only the most recent activity is of interest.
.. versionchanged:: 2.6
Added *maxlen*
Deque objects support the following methods: Deque objects support the following methods:
@ -168,8 +179,8 @@ Example::
.. _deque-recipes: .. _deque-recipes:
Recipes :class:`deque` Recipes
^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
This section shows various approaches to working with deques. This section shows various approaches to working with deques.
@ -186,42 +197,14 @@ To implement :class:`deque` slicing, use a similar approach applying
:meth:`rotate` to bring a target element to the left side of the deque. Remove :meth:`rotate` to bring a target element to the left side of the deque. Remove
old entries with :meth:`popleft`, add new entries with :meth:`extend`, and then old entries with :meth:`popleft`, add new entries with :meth:`extend`, and then
reverse the rotation. reverse the rotation.
With minor variations on that approach, it is easy to implement Forth style With minor variations on that approach, it is easy to implement Forth style
stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``, stack manipulations such as ``dup``, ``drop``, ``swap``, ``over``, ``pick``,
``rot``, and ``roll``. ``rot``, and ``roll``.
A roundrobin task server can be built from a :class:`deque` using
:meth:`popleft` to select the current task and :meth:`append` to add it back to
the tasklist if the input stream is not exhausted::
>>> def roundrobin(*iterables):
... pending = deque(iter(i) for i in iterables)
... while pending:
... task = pending.popleft()
... try:
... yield task.next()
... except StopIteration:
... continue
... pending.append(task)
...
>>> for value in roundrobin('abc', 'd', 'efgh'):
... print value
a
d
e
b
f
c
g
h
Multi-pass data reduction algorithms can be succinctly expressed and efficiently Multi-pass data reduction algorithms can be succinctly expressed and efficiently
coded by extracting elements with multiple calls to :meth:`popleft`, applying coded by extracting elements with multiple calls to :meth:`popleft`, applying
the reduction function, and calling :meth:`append` to add the result back to the a reduction function, and calling :meth:`append` to add the result back to the
queue. deque.
For example, building a balanced binary tree of nested lists entails reducing For example, building a balanced binary tree of nested lists entails reducing
two adjacent nodes into one by grouping them in a list:: two adjacent nodes into one by grouping them in a list::
@ -236,7 +219,12 @@ two adjacent nodes into one by grouping them in a list::
>>> print maketree('abcdefgh') >>> print maketree('abcdefgh')
[[[['a', 'b'], ['c', 'd']], [['e', 'f'], ['g', 'h']]]] [[[['a', 'b'], ['c', 'd']], [['e', 'f'], ['g', 'h']]]]
Bounded length deques provide functionality similar to the ``tail`` filter
in Unix::
def tail(filename, n=10):
'Return the last n lines of a file'
return deque(open(filename), n)
.. _defaultdict-objects: .. _defaultdict-objects:
@ -376,7 +364,8 @@ they add the ability to access fields by name instead of position index.
method which lists the tuple contents in a ``name=value`` format. method which lists the tuple contents in a ``name=value`` format.
The *fieldnames* are specified in a single string with each fieldname separated by The *fieldnames* are specified in a single string with each fieldname separated by
a space and/or comma. Any valid Python identifier may be used for a fieldname. a space and/or comma. Any valid Python identifier may be used for a fieldname
except for names starting and ending with double underscores.
If *verbose* is true, will print the class definition. If *verbose* is true, will print the class definition.
@ -387,7 +376,7 @@ they add the ability to access fields by name instead of position index.
Example:: Example::
>>> Point = NamedTuple('Point', 'x y', True) >>> Point = NamedTuple('Point', 'x y', verbose=True)
class Point(tuple): class Point(tuple):
'Point(x, y)' 'Point(x, y)'
__slots__ = () __slots__ = ()
@ -396,6 +385,9 @@ Example::
return tuple.__new__(cls, (x, y)) return tuple.__new__(cls, (x, y))
def __repr__(self): def __repr__(self):
return 'Point(x=%r, y=%r)' % self return 'Point(x=%r, y=%r)' % self
def __asdict__(self):
'Return a new dict mapping field names to their values'
return dict(zip(('x', 'y'), self))
def __replace__(self, field, value): def __replace__(self, field, value):
'Return a new Point object replacing one field with a new value' 'Return a new Point object replacing one field with a new value'
return Point(**dict(zip(('x', 'y'), self) + [(field, value)])) return Point(**dict(zip(('x', 'y'), self) + [(field, value)]))
@ -429,10 +421,25 @@ the values::
>>> Point(*t) # the star-operator unpacks any iterable object >>> Point(*t) # the star-operator unpacks any iterable object
Point(x=11, y=22) Point(x=11, y=22)
In addition to the methods inherited from tuples, named tuples support When casting a dictionary to a *NamedTuple*, use the double-star-operator::
an additonal method and an informational read-only attribute.
.. method:: somenamedtuple.replace(field, value) >>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)
In addition to the methods inherited from tuples, named tuples support
additonal methods and a read-only attribute.
.. method:: somenamedtuple.__asdict__()
Return a new dict which maps field names to their corresponding values:
::
>>> p.__asdict__()
{'x': 11, 'y': 22}
.. method:: somenamedtuple.__replace__(field, value)
Return a new instance of the named tuple replacing the named *field* with a new *value*: Return a new instance of the named tuple replacing the named *field* with a new *value*:
@ -447,20 +454,16 @@ an additonal method and an informational read-only attribute.
.. attribute:: somenamedtuple.__fields__ .. attribute:: somenamedtuple.__fields__
Return a tuple of strings listing the field names. This is useful for introspection, Return a tuple of strings listing the field names. This is useful for introspection
for converting a named tuple instance to a dictionary, and for combining named tuple and for creating new named tuple types from existing named tuples.
types to create new named tuple types:
:: ::
>>> p.__fields__ # view the field names >>> p.__fields__ # view the field names
('x', 'y') ('x', 'y')
>>> dict(zip(p.__fields__, p)) # convert to a dictionary
{'y': 22, 'x': 11}
>>> Color = NamedTuple('Color', 'red green blue') >>> Color = NamedTuple('Color', 'red green blue')
>>> pixel_fields = ' '.join(Point.__fields__ + Color.__fields__) # combine fields >>> Pixel = NamedTuple('Pixel', ' '.join(Point.__fields__ + Color.__fields__))
>>> Pixel = NamedTuple('Pixel', pixel_fields)
>>> Pixel(11, 22, 128, 255, 0) >>> Pixel(11, 22, 128, 255, 0)
Pixel(x=11, y=22, red=128, green=255, blue=0)' Pixel(x=11, y=22, red=128, green=255, blue=0)'

View File

@ -18,19 +18,21 @@ def NamedTuple(typename, s, verbose=False):
(11, 22) (11, 22)
>>> p.x + p.y # fields also accessable by name >>> p.x + p.y # fields also accessable by name
33 33
>>> p # readable __repr__ with name=value style >>> d = p.__asdict__() # convert to a dictionary
>>> d['x']
11
>>> Point(**d) # convert from a dictionary
Point(x=11, y=22) Point(x=11, y=22)
>>> p.__replace__('x', 100) # __replace__() is like str.replace() but targets a named field >>> p.__replace__('x', 100) # __replace__() is like str.replace() but targets a named field
Point(x=100, y=22) Point(x=100, y=22)
>>> d = dict(zip(p.__fields__, p)) # use __fields__ to make a dictionary
>>> d['x']
11
""" """
field_names = tuple(s.replace(',', ' ').split()) # names separated by spaces and/or commas field_names = tuple(s.replace(',', ' ').split()) # names separated by spaces and/or commas
if not ''.join((typename,) + field_names).replace('_', '').isalnum(): if not ''.join((typename,) + field_names).replace('_', '').isalnum():
raise ValueError('Type names and field names can only contain alphanumeric characters and underscores') raise ValueError('Type names and field names can only contain alphanumeric characters and underscores')
if any(name.startswith('__') and name.endswith('__') for name in field_names):
raise ValueError('Field names cannot start and end with double underscores')
argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes
reprtxt = ', '.join('%s=%%r' % name for name in field_names) reprtxt = ', '.join('%s=%%r' % name for name in field_names)
template = '''class %(typename)s(tuple): template = '''class %(typename)s(tuple):
@ -41,7 +43,10 @@ def NamedTuple(typename, s, verbose=False):
return tuple.__new__(cls, (%(argtxt)s)) return tuple.__new__(cls, (%(argtxt)s))
def __repr__(self): def __repr__(self):
return '%(typename)s(%(reprtxt)s)' %% self return '%(typename)s(%(reprtxt)s)' %% self
def __replace__(self, field, value): def __asdict__(self, dict=dict, zip=zip):
'Return a new dict mapping field names to their values'
return dict(zip(%(field_names)r, self))
def __replace__(self, field, value, dict=dict, zip=zip):
'Return a new %(typename)s object replacing one field with a new value' 'Return a new %(typename)s object replacing one field with a new value'
return %(typename)s(**dict(zip(%(field_names)r, self) + [(field, value)])) \n''' % locals() return %(typename)s(**dict(zip(%(field_names)r, self) + [(field, value)])) \n''' % locals()
for i, name in enumerate(field_names): for i, name in enumerate(field_names):

View File

@ -13,6 +13,7 @@ class TestNamedTuple(unittest.TestCase):
self.assertEqual(Point.__getitem__, tuple.__getitem__) self.assertEqual(Point.__getitem__, tuple.__getitem__)
self.assertRaises(ValueError, NamedTuple, 'abc%', 'def ghi') self.assertRaises(ValueError, NamedTuple, 'abc%', 'def ghi')
self.assertRaises(ValueError, NamedTuple, 'abc', 'def g%hi') self.assertRaises(ValueError, NamedTuple, 'abc', 'def g%hi')
self.assertRaises(ValueError, NamedTuple, 'abc', '__def__ ghi')
NamedTuple('Point0', 'x1 y2') # Verify that numbers are allowed in names NamedTuple('Point0', 'x1 y2') # Verify that numbers are allowed in names
def test_instance(self): def test_instance(self):
@ -32,6 +33,7 @@ class TestNamedTuple(unittest.TestCase):
self.assert_('__weakref__' not in dir(p)) self.assert_('__weakref__' not in dir(p))
self.assertEqual(p.__fields__, ('x', 'y')) # test __fields__ attribute self.assertEqual(p.__fields__, ('x', 'y')) # test __fields__ attribute
self.assertEqual(p.__replace__('x', 1), (1, 22)) # test __replace__ method self.assertEqual(p.__replace__('x', 1), (1, 22)) # test __replace__ method
self.assertEqual(p.__asdict__(), dict(x=11, y=22)) # test __dict__ method
# verify that field string can have commas # verify that field string can have commas
Point = NamedTuple('Point', 'x, y') Point = NamedTuple('Point', 'x, y')

View File

@ -47,6 +47,44 @@ class TestBasic(unittest.TestCase):
self.assertEqual(right, range(150, 400)) self.assertEqual(right, range(150, 400))
self.assertEqual(list(d), range(50, 150)) self.assertEqual(list(d), range(50, 150))
def test_maxlen(self):
self.assertRaises(ValueError, deque, 'abc', -2)
d = deque(range(10), maxlen=3)
self.assertEqual(repr(d), 'deque([7, 8, 9], maxlen=3)')
self.assertEqual(list(d), range(7, 10))
self.assertEqual(d, deque(range(10), 3))
d.append(10)
self.assertEqual(list(d), range(8, 11))
d.appendleft(7)
self.assertEqual(list(d), range(7, 10))
d.extend([10, 11])
self.assertEqual(list(d), range(9, 12))
d.extendleft([8, 7])
self.assertEqual(list(d), range(7, 10))
d = deque(xrange(200), maxlen=10)
d.append(d)
try:
fo = open(test_support.TESTFN, "wb")
print >> fo, d,
fo.close()
fo = open(test_support.TESTFN, "rb")
self.assertEqual(fo.read(), repr(d))
finally:
fo.close()
os.remove(test_support.TESTFN)
d = deque(range(10), maxlen=-1)
self.assertEqual(repr(d), 'deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])')
try:
fo = open(test_support.TESTFN, "wb")
print >> fo, d,
fo.close()
fo = open(test_support.TESTFN, "rb")
self.assertEqual(fo.read(), repr(d))
finally:
fo.close()
os.remove(test_support.TESTFN)
def test_comparisons(self): def test_comparisons(self):
d = deque('xabc'); d.popleft() d = deque('xabc'); d.popleft()
for e in [d, deque('abc'), deque('ab'), deque(), list(d)]: for e in [d, deque('abc'), deque('ab'), deque(), list(d)]:
@ -254,7 +292,7 @@ class TestBasic(unittest.TestCase):
os.remove(test_support.TESTFN) os.remove(test_support.TESTFN)
def test_init(self): def test_init(self):
self.assertRaises(TypeError, deque, 'abc', 2); self.assertRaises(TypeError, deque, 'abc', 2, 3);
self.assertRaises(TypeError, deque, 1); self.assertRaises(TypeError, deque, 1);
def test_hash(self): def test_hash(self):
@ -339,13 +377,13 @@ class TestBasic(unittest.TestCase):
self.assertNotEqual(id(d), id(e)) self.assertNotEqual(id(d), id(e))
self.assertEqual(list(d), list(e)) self.assertEqual(list(d), list(e))
def test_pickle_recursive(self): ## def test_pickle_recursive(self):
d = deque('abc') ## d = deque('abc')
d.append(d) ## d.append(d)
for i in (0, 1, 2): ## for i in (0, 1, 2):
e = pickle.loads(pickle.dumps(d, i)) ## e = pickle.loads(pickle.dumps(d, i))
self.assertNotEqual(id(d), id(e)) ## self.assertNotEqual(id(d), id(e))
self.assertEqual(id(e), id(e[-1])) ## self.assertEqual(id(e), id(e[-1]))
def test_deepcopy(self): def test_deepcopy(self):
mut = [10] mut = [10]
@ -451,24 +489,24 @@ class TestSubclass(unittest.TestCase):
self.assertEqual(type(d), type(e)) self.assertEqual(type(d), type(e))
self.assertEqual(list(d), list(e)) self.assertEqual(list(d), list(e))
def test_pickle(self): ## def test_pickle(self):
d = Deque('abc') ## d = Deque('abc')
d.append(d) ## d.append(d)
##
e = pickle.loads(pickle.dumps(d)) ## e = pickle.loads(pickle.dumps(d))
self.assertNotEqual(id(d), id(e)) ## self.assertNotEqual(id(d), id(e))
self.assertEqual(type(d), type(e)) ## self.assertEqual(type(d), type(e))
dd = d.pop() ## dd = d.pop()
ee = e.pop() ## ee = e.pop()
self.assertEqual(id(e), id(ee)) ## self.assertEqual(id(e), id(ee))
self.assertEqual(d, e) ## self.assertEqual(d, e)
##
d.x = d ## d.x = d
e = pickle.loads(pickle.dumps(d)) ## e = pickle.loads(pickle.dumps(d))
self.assertEqual(id(e), id(e.x)) ## self.assertEqual(id(e), id(e.x))
##
d = DequeWithBadIter('abc') ## d = DequeWithBadIter('abc')
self.assertRaises(TypeError, pickle.dumps, d) ## self.assertRaises(TypeError, pickle.dumps, d)
def test_weakref(self): def test_weakref(self):
d = deque('gallahad') d = deque('gallahad')

View File

@ -270,6 +270,8 @@ Core and builtins
Library Library
------- -------
- collections.deque() now supports a "maxlen" argument.
- itertools.count() is no longer bounded to LONG_MAX. Formerly, it raised - itertools.count() is no longer bounded to LONG_MAX. Formerly, it raised
an OverflowError. Now, automatically shifts from ints to longs. an OverflowError. Now, automatically shifts from ints to longs.

View File

@ -83,10 +83,27 @@ typedef struct {
int leftindex; /* in range(BLOCKLEN) */ int leftindex; /* in range(BLOCKLEN) */
int rightindex; /* in range(BLOCKLEN) */ int rightindex; /* in range(BLOCKLEN) */
int len; int len;
int maxlen;
long state; /* incremented whenever the indices move */ long state; /* incremented whenever the indices move */
PyObject *weakreflist; /* List of weak references */ PyObject *weakreflist; /* List of weak references */
} dequeobject; } dequeobject;
/* The deque's size limit is d.maxlen. The limit can be zero or positive.
* If there is no limit, then d.maxlen == -1.
*
* After an item is added to a deque, we check to see if the size has grown past
* the limit. If it has, we get the size back down to the limit by popping an
* item off of the opposite end. The methods that can trigger this are append(),
* appendleft(), extend(), and extendleft().
*/
#define TRIM(d, popfunction) \
if (d->maxlen != -1 && d->len > d->maxlen) { \
PyObject *rv = popfunction(d, NULL); \
assert(rv != NULL && d->len <= d->maxlen); \
Py_DECREF(rv); \
}
static PyTypeObject deque_type; static PyTypeObject deque_type;
static PyObject * static PyObject *
@ -95,9 +112,6 @@ deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
dequeobject *deque; dequeobject *deque;
block *b; block *b;
if (type == &deque_type && !_PyArg_NoKeywords("deque()", kwds))
return NULL;
/* create dequeobject structure */ /* create dequeobject structure */
deque = (dequeobject *)type->tp_alloc(type, 0); deque = (dequeobject *)type->tp_alloc(type, 0);
if (deque == NULL) if (deque == NULL)
@ -117,54 +131,11 @@ deque_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
deque->len = 0; deque->len = 0;
deque->state = 0; deque->state = 0;
deque->weakreflist = NULL; deque->weakreflist = NULL;
deque->maxlen = -1;
return (PyObject *)deque; return (PyObject *)deque;
} }
static PyObject *
deque_append(dequeobject *deque, PyObject *item)
{
deque->state++;
if (deque->rightindex == BLOCKLEN-1) {
block *b = newblock(deque->rightblock, NULL, deque->len);
if (b == NULL)
return NULL;
assert(deque->rightblock->rightlink == NULL);
deque->rightblock->rightlink = b;
deque->rightblock = b;
deque->rightindex = -1;
}
Py_INCREF(item);
deque->len++;
deque->rightindex++;
deque->rightblock->data[deque->rightindex] = item;
Py_RETURN_NONE;
}
PyDoc_STRVAR(append_doc, "Add an element to the right side of the deque.");
static PyObject *
deque_appendleft(dequeobject *deque, PyObject *item)
{
deque->state++;
if (deque->leftindex == 0) {
block *b = newblock(NULL, deque->leftblock, deque->len);
if (b == NULL)
return NULL;
assert(deque->leftblock->leftlink == NULL);
deque->leftblock->leftlink = b;
deque->leftblock = b;
deque->leftindex = BLOCKLEN;
}
Py_INCREF(item);
deque->len++;
deque->leftindex--;
deque->leftblock->data[deque->leftindex] = item;
Py_RETURN_NONE;
}
PyDoc_STRVAR(appendleft_doc, "Add an element to the left side of the deque.");
static PyObject * static PyObject *
deque_pop(dequeobject *deque, PyObject *unused) deque_pop(dequeobject *deque, PyObject *unused)
{ {
@ -239,6 +210,52 @@ deque_popleft(dequeobject *deque, PyObject *unused)
PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element."); PyDoc_STRVAR(popleft_doc, "Remove and return the leftmost element.");
static PyObject *
deque_append(dequeobject *deque, PyObject *item)
{
deque->state++;
if (deque->rightindex == BLOCKLEN-1) {
block *b = newblock(deque->rightblock, NULL, deque->len);
if (b == NULL)
return NULL;
assert(deque->rightblock->rightlink == NULL);
deque->rightblock->rightlink = b;
deque->rightblock = b;
deque->rightindex = -1;
}
Py_INCREF(item);
deque->len++;
deque->rightindex++;
deque->rightblock->data[deque->rightindex] = item;
TRIM(deque, deque_popleft);
Py_RETURN_NONE;
}
PyDoc_STRVAR(append_doc, "Add an element to the right side of the deque.");
static PyObject *
deque_appendleft(dequeobject *deque, PyObject *item)
{
deque->state++;
if (deque->leftindex == 0) {
block *b = newblock(NULL, deque->leftblock, deque->len);
if (b == NULL)
return NULL;
assert(deque->leftblock->leftlink == NULL);
deque->leftblock->leftlink = b;
deque->leftblock = b;
deque->leftindex = BLOCKLEN;
}
Py_INCREF(item);
deque->len++;
deque->leftindex--;
deque->leftblock->data[deque->leftindex] = item;
TRIM(deque, deque_pop);
Py_RETURN_NONE;
}
PyDoc_STRVAR(appendleft_doc, "Add an element to the left side of the deque.");
static PyObject * static PyObject *
deque_extend(dequeobject *deque, PyObject *iterable) deque_extend(dequeobject *deque, PyObject *iterable)
{ {
@ -266,6 +283,7 @@ deque_extend(dequeobject *deque, PyObject *iterable)
deque->len++; deque->len++;
deque->rightindex++; deque->rightindex++;
deque->rightblock->data[deque->rightindex] = item; deque->rightblock->data[deque->rightindex] = item;
TRIM(deque, deque_popleft);
} }
Py_DECREF(it); Py_DECREF(it);
if (PyErr_Occurred()) if (PyErr_Occurred())
@ -303,6 +321,7 @@ deque_extendleft(dequeobject *deque, PyObject *iterable)
deque->len++; deque->len++;
deque->leftindex--; deque->leftindex--;
deque->leftblock->data[deque->leftindex] = item; deque->leftblock->data[deque->leftindex] = item;
TRIM(deque, deque_pop);
} }
Py_DECREF(it); Py_DECREF(it);
if (PyErr_Occurred()) if (PyErr_Occurred())
@ -579,8 +598,8 @@ deque_nohash(PyObject *self)
static PyObject * static PyObject *
deque_copy(PyObject *deque) deque_copy(PyObject *deque)
{ {
return PyObject_CallFunctionObjArgs((PyObject *)(Py_Type(deque)), return PyObject_CallFunction((PyObject *)(Py_Type(deque)), "Oi",
deque, NULL); deque, ((dequeobject *)deque)->maxlen, NULL);
} }
PyDoc_STRVAR(copy_doc, "Return a shallow copy of a deque."); PyDoc_STRVAR(copy_doc, "Return a shallow copy of a deque.");
@ -588,21 +607,22 @@ PyDoc_STRVAR(copy_doc, "Return a shallow copy of a deque.");
static PyObject * static PyObject *
deque_reduce(dequeobject *deque) deque_reduce(dequeobject *deque)
{ {
PyObject *dict, *result, *it; PyObject *dict, *result, *aslist;
dict = PyObject_GetAttrString((PyObject *)deque, "__dict__"); dict = PyObject_GetAttrString((PyObject *)deque, "__dict__");
if (dict == NULL) { if (dict == NULL)
PyErr_Clear(); PyErr_Clear();
dict = Py_None; aslist = PySequence_List((PyObject *)deque);
Py_INCREF(dict); if (aslist == NULL) {
}
it = PyObject_GetIter((PyObject *)deque);
if (it == NULL) {
Py_DECREF(dict); Py_DECREF(dict);
return NULL; return NULL;
} }
result = Py_BuildValue("O()ON", Py_Type(deque), dict, it); if (dict == NULL)
Py_DECREF(dict); result = Py_BuildValue("O(Oi)", Py_Type(deque), aslist, deque->maxlen);
else
result = Py_BuildValue("O(Oi)O", Py_Type(deque), aslist, deque->maxlen, dict);
Py_XDECREF(dict);
Py_DECREF(aslist);
return result; return result;
} }
@ -611,7 +631,7 @@ PyDoc_STRVAR(reduce_doc, "Return state information for pickling.");
static PyObject * static PyObject *
deque_repr(PyObject *deque) deque_repr(PyObject *deque)
{ {
PyObject *aslist, *result, *fmt; PyObject *aslist, *result, *fmt; /*, *limit; */
int i; int i;
i = Py_ReprEnter(deque); i = Py_ReprEnter(deque);
@ -626,14 +646,17 @@ deque_repr(PyObject *deque)
Py_ReprLeave(deque); Py_ReprLeave(deque);
return NULL; return NULL;
} }
if (((dequeobject *)deque)->maxlen != -1)
fmt = PyString_FromString("deque(%r)"); fmt = PyString_FromFormat("deque(%%r, maxlen=%i)",
((dequeobject *)deque)->maxlen);
else
fmt = PyString_FromString("deque(%r)");
if (fmt == NULL) { if (fmt == NULL) {
Py_DECREF(aslist); Py_DECREF(aslist);
Py_ReprLeave(deque); Py_ReprLeave(deque);
return NULL; return NULL;
} }
result = PyString_Format(fmt, aslist); result = PyString_Format(fmt, aslist);
Py_DECREF(fmt); Py_DECREF(fmt);
Py_DECREF(aslist); Py_DECREF(aslist);
Py_ReprLeave(deque); Py_ReprLeave(deque);
@ -652,9 +675,7 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags)
if (i != 0) { if (i != 0) {
if (i < 0) if (i < 0)
return i; return i;
Py_BEGIN_ALLOW_THREADS
fputs("[...]", fp); fputs("[...]", fp);
Py_END_ALLOW_THREADS
return 0; return 0;
} }
@ -662,13 +683,9 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags)
if (it == NULL) if (it == NULL)
return -1; return -1;
Py_BEGIN_ALLOW_THREADS
fputs("deque([", fp); fputs("deque([", fp);
Py_END_ALLOW_THREADS
while ((item = PyIter_Next(it)) != NULL) { while ((item = PyIter_Next(it)) != NULL) {
Py_BEGIN_ALLOW_THREADS
fputs(emit, fp); fputs(emit, fp);
Py_END_ALLOW_THREADS
emit = separator; emit = separator;
if (PyObject_Print(item, fp, 0) != 0) { if (PyObject_Print(item, fp, 0) != 0) {
Py_DECREF(item); Py_DECREF(item);
@ -682,9 +699,11 @@ deque_tp_print(PyObject *deque, FILE *fp, int flags)
Py_DECREF(it); Py_DECREF(it);
if (PyErr_Occurred()) if (PyErr_Occurred())
return -1; return -1;
Py_BEGIN_ALLOW_THREADS
fputs("])", fp); if (((dequeobject *)deque)->maxlen == -1)
Py_END_ALLOW_THREADS fputs("])", fp);
else
fprintf(fp, "], maxlen=%d)", ((dequeobject *)deque)->maxlen);
return 0; return 0;
} }
@ -767,13 +786,19 @@ done:
} }
static int static int
deque_init(dequeobject *deque, PyObject *args, PyObject *kwds) deque_init(dequeobject *deque, PyObject *args, PyObject *kwdargs)
{ {
PyObject *iterable = NULL; PyObject *iterable = NULL;
int maxlen = -1;
char *kwlist[] = {"iterable", "maxlen", 0};
if (!PyArg_UnpackTuple(args, "deque", 0, 1, &iterable)) if (!PyArg_ParseTupleAndKeywords(args, kwdargs, "|Oi:deque", kwlist, &iterable, &maxlen))
return -1; return -1;
if (maxlen < -1) {
PyErr_SetString(PyExc_ValueError, "maxlen must be -1 or greater");
return -1;
}
deque->maxlen = maxlen;
if (iterable != NULL) { if (iterable != NULL) {
PyObject *rv = deque_extend(deque, iterable); PyObject *rv = deque_extend(deque, iterable);
if (rv == NULL) if (rv == NULL)
@ -828,7 +853,7 @@ static PyMethodDef deque_methods[] = {
}; };
PyDoc_STRVAR(deque_doc, PyDoc_STRVAR(deque_doc,
"deque(iterable) --> deque object\n\ "deque(iterable[, maxlen]) --> deque object\n\
\n\ \n\
Build an ordered collection accessible from endpoints only."); Build an ordered collection accessible from endpoints only.");
@ -1198,24 +1223,15 @@ static int
defdict_print(defdictobject *dd, FILE *fp, int flags) defdict_print(defdictobject *dd, FILE *fp, int flags)
{ {
int sts; int sts;
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "defaultdict("); fprintf(fp, "defaultdict(");
Py_END_ALLOW_THREADS if (dd->default_factory == NULL)
if (dd->default_factory == NULL) {
Py_BEGIN_ALLOW_THREADS
fprintf(fp, "None"); fprintf(fp, "None");
Py_END_ALLOW_THREADS
}
else { else {
PyObject_Print(dd->default_factory, fp, 0); PyObject_Print(dd->default_factory, fp, 0);
} }
Py_BEGIN_ALLOW_THREADS
fprintf(fp, ", "); fprintf(fp, ", ");
Py_END_ALLOW_THREADS
sts = PyDict_Type.tp_print((PyObject *)dd, fp, 0); sts = PyDict_Type.tp_print((PyObject *)dd, fp, 0);
Py_BEGIN_ALLOW_THREADS
fprintf(fp, ")"); fprintf(fp, ")");
Py_END_ALLOW_THREADS
return sts; return sts;
} }