[py3] Updated dict-like data structures for Python 3.

The keys/items/values methods return iterators in Python 3, and the
iterkeys/items/values methods don't exist in Python 3. The behavior
under Python 2 is unchanged.
This commit is contained in:
Aymeric Augustin 2012-07-25 09:12:59 +02:00
parent 4b5cb116e3
commit ab6cd1c839
4 changed files with 140 additions and 87 deletions

View File

@ -1,6 +1,7 @@
import copy import copy
import warnings import warnings
from types import GeneratorType from types import GeneratorType
from django.utils import six
class MergeDict(object): class MergeDict(object):
@ -31,30 +32,40 @@ class MergeDict(object):
except KeyError: except KeyError:
return default return default
# This is used by MergeDicts of MultiValueDicts.
def getlist(self, key): def getlist(self, key):
for dict_ in self.dicts: for dict_ in self.dicts:
if key in dict_.keys(): if key in dict_:
return dict_.getlist(key) return dict_.getlist(key)
return [] return []
def iteritems(self): def _iteritems(self):
seen = set() seen = set()
for dict_ in self.dicts: for dict_ in self.dicts:
for item in dict_.iteritems(): for item in six.iteritems(dict_):
k, v = item k = item[0]
if k in seen: if k in seen:
continue continue
seen.add(k) seen.add(k)
yield item yield item
def iterkeys(self): def _iterkeys(self):
for k, v in self.iteritems(): for k, v in self._iteritems():
yield k yield k
def itervalues(self): def _itervalues(self):
for k, v in self.iteritems(): for k, v in self._iteritems():
yield v yield v
if six.PY3:
items = _iteritems
keys = _iterkeys
values = _itervalues
else:
iteritems = _iteritems
iterkeys = _iterkeys
itervalues = _itervalues
def items(self): def items(self):
return list(self.iteritems()) return list(self.iteritems())
@ -71,7 +82,8 @@ class MergeDict(object):
return False return False
__contains__ = has_key __contains__ = has_key
__iter__ = iterkeys
__iter__ = _iterkeys
def copy(self): def copy(self):
"""Returns a copy of this object.""" """Returns a copy of this object."""
@ -117,7 +129,7 @@ class SortedDict(dict):
data = list(data) data = list(data)
super(SortedDict, self).__init__(data) super(SortedDict, self).__init__(data)
if isinstance(data, dict): if isinstance(data, dict):
self.keyOrder = data.keys() self.keyOrder = list(six.iterkeys(data))
else: else:
self.keyOrder = [] self.keyOrder = []
seen = set() seen = set()
@ -128,7 +140,7 @@ class SortedDict(dict):
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self.__class__([(key, copy.deepcopy(value, memo)) return self.__class__([(key, copy.deepcopy(value, memo))
for key, value in self.iteritems()]) for key, value in six.iteritems(self)])
def __copy__(self): def __copy__(self):
# The Python's default copy implementation will alter the state # The Python's default copy implementation will alter the state
@ -162,28 +174,38 @@ class SortedDict(dict):
self.keyOrder.remove(result[0]) self.keyOrder.remove(result[0])
return result return result
def items(self): def _iteritems(self):
return zip(self.keyOrder, self.values())
def iteritems(self):
for key in self.keyOrder: for key in self.keyOrder:
yield key, self[key] yield key, self[key]
def keys(self): def _iterkeys(self):
return self.keyOrder[:] for key in self.keyOrder:
yield key
def iterkeys(self): def _itervalues(self):
return iter(self.keyOrder)
def values(self):
return map(self.__getitem__, self.keyOrder)
def itervalues(self):
for key in self.keyOrder: for key in self.keyOrder:
yield self[key] yield self[key]
if six.PY3:
items = _iteritems
keys = _iterkeys
values = _itervalues
else:
iteritems = _iteritems
iterkeys = _iterkeys
itervalues = _itervalues
def items(self):
return list(self.iteritems())
def keys(self):
return list(self.iterkeys())
def values(self):
return list(self.itervalues())
def update(self, dict_): def update(self, dict_):
for k, v in dict_.iteritems(): for k, v in six.iteritems(dict_):
self[k] = v self[k] = v
def setdefault(self, key, default): def setdefault(self, key, default):
@ -226,7 +248,7 @@ class SortedDict(dict):
Replaces the normal dict.__repr__ with a version that returns the keys Replaces the normal dict.__repr__ with a version that returns the keys
in their sorted order. in their sorted order.
""" """
return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)])
def clear(self): def clear(self):
super(SortedDict, self).clear() super(SortedDict, self).clear()
@ -356,37 +378,40 @@ class MultiValueDict(dict):
"""Appends an item to the internal list associated with key.""" """Appends an item to the internal list associated with key."""
self.setlistdefault(key).append(value) self.setlistdefault(key).append(value)
def items(self): def _iteritems(self):
"""
Returns a list of (key, value) pairs, where value is the last item in
the list associated with the key.
"""
return [(key, self[key]) for key in self.keys()]
def iteritems(self):
""" """
Yields (key, value) pairs, where value is the last item in the list Yields (key, value) pairs, where value is the last item in the list
associated with the key. associated with the key.
""" """
for key in self.keys(): for key in self:
yield (key, self[key]) yield key, self[key]
def _iterlists(self):
"""Yields (key, list) pairs."""
return six.iteritems(super(MultiValueDict, self))
def _itervalues(self):
"""Yield the last value on every key list."""
for key in self:
yield self[key]
if six.PY3:
items = _iteritems
lists = _iterlists
values = _itervalues
else:
iteritems = _iteritems
iterlists = _iterlists
itervalues = _itervalues
def items(self):
return list(self.iteritems())
def lists(self): def lists(self):
"""Returns a list of (key, list) pairs.""" return list(self.iterlists())
return super(MultiValueDict, self).items()
def iterlists(self):
"""Yields (key, list) pairs."""
return super(MultiValueDict, self).iteritems()
def values(self): def values(self):
"""Returns a list of the last value on every key list.""" return list(self.itervalues())
return [self[key] for key in self.keys()]
def itervalues(self):
"""Yield the last value on every key list."""
for key in self.iterkeys():
yield self[key]
def copy(self): def copy(self):
"""Returns a shallow copy of this object.""" """Returns a shallow copy of this object."""
@ -410,7 +435,7 @@ class MultiValueDict(dict):
self.setlistdefault(key).append(value) self.setlistdefault(key).append(value)
except TypeError: except TypeError:
raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary")
for key, value in kwargs.iteritems(): for key, value in six.iteritems(kwargs):
self.setlistdefault(key).append(value) self.setlistdefault(key).append(value)
def dict(self): def dict(self):

View File

@ -355,4 +355,13 @@ def with_metaclass(meta, base=object):
### Additional customizations for Django ### ### Additional customizations for Django ###
if PY3:
_iterlists = "lists"
else:
_iterlists = "iterlists"
def iterlists(d):
"""Return an iterator over the values of a MultiValueDict."""
return getattr(d, _iterlists)()
add_move(MovedModule("_dummy_thread", "dummy_thread")) add_move(MovedModule("_dummy_thread", "dummy_thread"))

View File

@ -120,3 +120,18 @@ If you need different code in Python 2 and Python 3, check :data:`six.PY3`::
This is a last resort solution when :mod:`six` doesn't provide an appropriate This is a last resort solution when :mod:`six` doesn't provide an appropriate
function. function.
.. module:: django.utils.six
Customizations of six
=====================
The version of six bundled with Django includes a few additional tools:
.. function:: iterlists(MultiValueDict)
Returns an iterator over the lists of values of a
:class:`~django.utils.datastructures.MultiValueDict`. This replaces
:meth:`~django.utils.datastructures.MultiValueDict.iterlists()` on Python
2 and :meth:`~django.utils.datastructures.MultiValueDict.lists()` on
Python 3.

View File

@ -9,6 +9,7 @@ import warnings
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.utils.datastructures import (DictWrapper, ImmutableList, from django.utils.datastructures import (DictWrapper, ImmutableList,
MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict) MultiValueDict, MultiValueDictKeyError, MergeDict, SortedDict)
from django.utils import six
class SortedDictTests(SimpleTestCase): class SortedDictTests(SimpleTestCase):
@ -25,19 +26,19 @@ class SortedDictTests(SimpleTestCase):
self.d2[7] = 'seven' self.d2[7] = 'seven'
def test_basic_methods(self): def test_basic_methods(self):
self.assertEqual(self.d1.keys(), [7, 1, 9]) self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9])
self.assertEqual(self.d1.values(), ['seven', 'one', 'nine']) self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'one', 'nine'])
self.assertEqual(self.d1.items(), [(7, 'seven'), (1, 'one'), (9, 'nine')]) self.assertEqual(list(six.iteritems(self.d1)), [(7, 'seven'), (1, 'one'), (9, 'nine')])
def test_overwrite_ordering(self): def test_overwrite_ordering(self):
""" Overwriting an item keeps it's place. """ """ Overwriting an item keeps its place. """
self.d1[1] = 'ONE' self.d1[1] = 'ONE'
self.assertEqual(self.d1.values(), ['seven', 'ONE', 'nine']) self.assertEqual(list(six.itervalues(self.d1)), ['seven', 'ONE', 'nine'])
def test_append_items(self): def test_append_items(self):
""" New items go to the end. """ """ New items go to the end. """
self.d1[0] = 'nil' self.d1[0] = 'nil'
self.assertEqual(self.d1.keys(), [7, 1, 9, 0]) self.assertEqual(list(six.iterkeys(self.d1)), [7, 1, 9, 0])
def test_delete_and_insert(self): def test_delete_and_insert(self):
""" """
@ -45,14 +46,18 @@ class SortedDictTests(SimpleTestCase):
at the end. at the end.
""" """
del self.d2[7] del self.d2[7]
self.assertEqual(self.d2.keys(), [1, 9, 0]) self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0])
self.d2[7] = 'lucky number 7' self.d2[7] = 'lucky number 7'
self.assertEqual(self.d2.keys(), [1, 9, 0, 7]) self.assertEqual(list(six.iterkeys(self.d2)), [1, 9, 0, 7])
if not six.PY3:
def test_change_keys(self): def test_change_keys(self):
""" """
Changing the keys won't do anything, it's only a copy of the Changing the keys won't do anything, it's only a copy of the
keys dict. keys dict.
This test doesn't make sense under Python 3 because keys is
an iterator.
""" """
k = self.d2.keys() k = self.d2.keys()
k.remove(9) k.remove(9)
@ -68,18 +73,18 @@ class SortedDictTests(SimpleTestCase):
tuples = ((2, 'two'), (1, 'one'), (2, 'second-two')) tuples = ((2, 'two'), (1, 'one'), (2, 'second-two'))
d = SortedDict(tuples) d = SortedDict(tuples)
self.assertEqual(d.keys(), [2, 1]) self.assertEqual(list(six.iterkeys(d)), [2, 1])
real_dict = dict(tuples) real_dict = dict(tuples)
self.assertEqual(sorted(real_dict.values()), ['one', 'second-two']) self.assertEqual(sorted(six.itervalues(real_dict)), ['one', 'second-two'])
# Here the order of SortedDict values *is* what we are testing # Here the order of SortedDict values *is* what we are testing
self.assertEqual(d.values(), ['second-two', 'one']) self.assertEqual(list(six.itervalues(d)), ['second-two', 'one'])
def test_overwrite(self): def test_overwrite(self):
self.d1[1] = 'not one' self.d1[1] = 'not one'
self.assertEqual(self.d1[1], 'not one') self.assertEqual(self.d1[1], 'not one')
self.assertEqual(self.d1.keys(), self.d1.copy().keys()) self.assertEqual(list(six.iterkeys(self.d1)), list(six.iterkeys(self.d1.copy())))
def test_append(self): def test_append(self):
self.d1[13] = 'thirteen' self.d1[13] = 'thirteen'
@ -115,8 +120,8 @@ class SortedDictTests(SimpleTestCase):
def test_copy(self): def test_copy(self):
orig = SortedDict(((1, "one"), (0, "zero"), (2, "two"))) orig = SortedDict(((1, "one"), (0, "zero"), (2, "two")))
copied = copy.copy(orig) copied = copy.copy(orig)
self.assertEqual(orig.keys(), [1, 0, 2]) self.assertEqual(list(six.iterkeys(orig)), [1, 0, 2])
self.assertEqual(copied.keys(), [1, 0, 2]) self.assertEqual(list(six.iterkeys(copied)), [1, 0, 2])
def test_clear(self): def test_clear(self):
self.d1.clear() self.d1.clear()
@ -178,12 +183,12 @@ class MergeDictTests(SimpleTestCase):
self.assertEqual(mm.getlist('key4'), ['value5', 'value6']) self.assertEqual(mm.getlist('key4'), ['value5', 'value6'])
self.assertEqual(mm.getlist('undefined'), []) self.assertEqual(mm.getlist('undefined'), [])
self.assertEqual(sorted(mm.keys()), ['key1', 'key2', 'key4']) self.assertEqual(sorted(six.iterkeys(mm)), ['key1', 'key2', 'key4'])
self.assertEqual(len(mm.values()), 3) self.assertEqual(len(list(six.itervalues(mm))), 3)
self.assertTrue('value1' in mm.values()) self.assertTrue('value1' in six.itervalues(mm))
self.assertEqual(sorted(mm.items(), key=lambda k: k[0]), self.assertEqual(sorted(six.iteritems(mm), key=lambda k: k[0]),
[('key1', 'value1'), ('key2', 'value3'), [('key1', 'value1'), ('key2', 'value3'),
('key4', 'value6')]) ('key4', 'value6')])
@ -201,10 +206,10 @@ class MultiValueDictTests(SimpleTestCase):
self.assertEqual(d['name'], 'Simon') self.assertEqual(d['name'], 'Simon')
self.assertEqual(d.get('name'), 'Simon') self.assertEqual(d.get('name'), 'Simon')
self.assertEqual(d.getlist('name'), ['Adrian', 'Simon']) self.assertEqual(d.getlist('name'), ['Adrian', 'Simon'])
self.assertEqual(list(d.iteritems()), self.assertEqual(list(six.iteritems(d)),
[('position', 'Developer'), ('name', 'Simon')]) [('position', 'Developer'), ('name', 'Simon')])
self.assertEqual(list(d.iterlists()), self.assertEqual(list(six.iterlists(d)),
[('position', ['Developer']), [('position', ['Developer']),
('name', ['Adrian', 'Simon'])]) ('name', ['Adrian', 'Simon'])])
@ -224,8 +229,7 @@ class MultiValueDictTests(SimpleTestCase):
d.setlist('lastname', ['Holovaty', 'Willison']) d.setlist('lastname', ['Holovaty', 'Willison'])
self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison']) self.assertEqual(d.getlist('lastname'), ['Holovaty', 'Willison'])
self.assertEqual(d.values(), ['Developer', 'Simon', 'Willison']) self.assertEqual(list(six.itervalues(d)),
self.assertEqual(list(d.itervalues()),
['Developer', 'Simon', 'Willison']) ['Developer', 'Simon', 'Willison'])
def test_appendlist(self): def test_appendlist(self):
@ -260,8 +264,8 @@ class MultiValueDictTests(SimpleTestCase):
'pm': ['Rory'], 'pm': ['Rory'],
}) })
d = mvd.dict() d = mvd.dict()
self.assertEqual(d.keys(), mvd.keys()) self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd)))
for key in mvd.keys(): for key in six.iterkeys(mvd):
self.assertEqual(d[key], mvd[key]) self.assertEqual(d[key], mvd[key])
self.assertEqual({}, MultiValueDict().dict()) self.assertEqual({}, MultiValueDict().dict())