Issue #22609: Constructors and update methods of mapping classes in the

collections module now accept the self keyword argument.
This commit is contained in:
Serhiy Storchaka 2014-11-27 16:25:51 +02:00
parent 4847035458
commit ae5cb214d2
4 changed files with 121 additions and 28 deletions

View file

@ -584,23 +584,24 @@ class MutableMapping(Mapping):
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
In either case, this is followed by: for k, v in F.items(): D[k] = v In either case, this is followed by: for k, v in F.items(): D[k] = v
''' '''
if len(args) > 2: if not args:
raise TypeError("update() takes at most 2 positional " raise TypeError("descriptor 'update' of 'MutableMapping' object "
"arguments ({} given)".format(len(args))) "needs an argument")
elif not args: self, *args = args
raise TypeError("update() takes at least 1 argument (0 given)") if len(args) > 1:
self = args[0] raise TypeError('update expected at most 1 arguments, got %d' %
other = args[1] if len(args) >= 2 else () len(args))
if args:
if isinstance(other, Mapping): other = args[0]
for key in other: if isinstance(other, Mapping):
self[key] = other[key] for key in other:
elif hasattr(other, "keys"): self[key] = other[key]
for key in other.keys(): elif hasattr(other, "keys"):
self[key] = other[key] for key in other.keys():
else: self[key] = other[key]
for key, value in other: else:
self[key] = value for key, value in other:
self[key] = value
for key, value in kwds.items(): for key, value in kwds.items():
self[key] = value self[key] = value

View file

@ -38,12 +38,16 @@ class OrderedDict(dict):
# Individual links are kept alive by the hard reference in self.__map. # Individual links are kept alive by the hard reference in self.__map.
# Those hard references disappear when a key is deleted from an OrderedDict. # Those hard references disappear when a key is deleted from an OrderedDict.
def __init__(self, *args, **kwds): def __init__(*args, **kwds):
'''Initialize an ordered dictionary. The signature is the same as '''Initialize an ordered dictionary. The signature is the same as
regular dictionaries, but keyword arguments are not recommended because regular dictionaries, but keyword arguments are not recommended because
their insertion order is arbitrary. their insertion order is arbitrary.
''' '''
if not args:
raise TypeError("descriptor '__init__' of 'OrderedDict' object "
"needs an argument")
self, *args = args
if len(args) > 1: if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args)) raise TypeError('expected at most 1 arguments, got %d' % len(args))
try: try:
@ -450,7 +454,7 @@ class Counter(dict):
# http://code.activestate.com/recipes/259174/ # http://code.activestate.com/recipes/259174/
# Knuth, TAOCP Vol. II section 4.6.3 # Knuth, TAOCP Vol. II section 4.6.3
def __init__(self, iterable=None, **kwds): def __init__(*args, **kwds):
'''Create a new, empty Counter object. And if given, count elements '''Create a new, empty Counter object. And if given, count elements
from an input iterable. Or, initialize the count from another mapping from an input iterable. Or, initialize the count from another mapping
of elements to their counts. of elements to their counts.
@ -461,8 +465,14 @@ class Counter(dict):
>>> c = Counter(a=4, b=2) # a new counter from keyword args >>> c = Counter(a=4, b=2) # a new counter from keyword args
''' '''
super().__init__() if not args:
self.update(iterable, **kwds) raise TypeError("descriptor '__init__' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
super(Counter, self).__init__()
self.update(*args, **kwds)
def __missing__(self, key): def __missing__(self, key):
'The count of elements not in the Counter is zero.' 'The count of elements not in the Counter is zero.'
@ -513,7 +523,7 @@ class Counter(dict):
raise NotImplementedError( raise NotImplementedError(
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
def update(self, iterable=None, **kwds): def update(*args, **kwds):
'''Like dict.update() but add counts instead of replacing them. '''Like dict.update() but add counts instead of replacing them.
Source can be an iterable, a dictionary, or another Counter instance. Source can be an iterable, a dictionary, or another Counter instance.
@ -533,6 +543,13 @@ class Counter(dict):
# contexts. Instead, we implement straight-addition. Both the inputs # contexts. Instead, we implement straight-addition. Both the inputs
# and outputs are allowed to contain zero and negative counts. # and outputs are allowed to contain zero and negative counts.
if not args:
raise TypeError("descriptor 'update' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None: if iterable is not None:
if isinstance(iterable, Mapping): if isinstance(iterable, Mapping):
if self: if self:
@ -540,13 +557,13 @@ class Counter(dict):
for elem, count in iterable.items(): for elem, count in iterable.items():
self[elem] = count + self_get(elem, 0) self[elem] = count + self_get(elem, 0)
else: else:
super().update(iterable) # fast path when counter is empty super(Counter, self).update(iterable) # fast path when counter is empty
else: else:
_count_elements(self, iterable) _count_elements(self, iterable)
if kwds: if kwds:
self.update(kwds) self.update(kwds)
def subtract(self, iterable=None, **kwds): def subtract(*args, **kwds):
'''Like dict.update() but subtracts counts instead of replacing them. '''Like dict.update() but subtracts counts instead of replacing them.
Counts can be reduced below zero. Both the inputs and outputs are Counts can be reduced below zero. Both the inputs and outputs are
allowed to contain zero and negative counts. allowed to contain zero and negative counts.
@ -562,6 +579,13 @@ class Counter(dict):
-1 -1
''' '''
if not args:
raise TypeError("descriptor 'subtract' of 'Counter' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
iterable = args[0] if args else None
if iterable is not None: if iterable is not None:
self_get = self.get self_get = self.get
if isinstance(iterable, Mapping): if isinstance(iterable, Mapping):
@ -869,7 +893,14 @@ class ChainMap(MutableMapping):
class UserDict(MutableMapping): class UserDict(MutableMapping):
# Start by filling-out the abstract methods # Start by filling-out the abstract methods
def __init__(self, dict=None, **kwargs): def __init__(*args, **kwargs):
if not args:
raise TypeError("descriptor '__init__' of 'UserDict' object "
"needs an argument")
self, *args = args
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
dict = args[0] if args else None
self.data = {} self.data = {}
if dict is not None: if dict is not None:
self.update(dict) self.update(dict)

View file

@ -1084,6 +1084,28 @@ class TestCounter(unittest.TestCase):
self.assertEqual(c.setdefault('e', 5), 5) self.assertEqual(c.setdefault('e', 5), 5)
self.assertEqual(c['e'], 5) self.assertEqual(c['e'], 5)
def test_init(self):
self.assertEqual(list(Counter(self=42).items()), [('self', 42)])
self.assertEqual(list(Counter(iterable=42).items()), [('iterable', 42)])
self.assertEqual(list(Counter(iterable=None).items()), [('iterable', None)])
self.assertRaises(TypeError, Counter, 42)
self.assertRaises(TypeError, Counter, (), ())
self.assertRaises(TypeError, Counter.__init__)
def test_update(self):
c = Counter()
c.update(self=42)
self.assertEqual(list(c.items()), [('self', 42)])
c = Counter()
c.update(iterable=42)
self.assertEqual(list(c.items()), [('iterable', 42)])
c = Counter()
c.update(iterable=None)
self.assertEqual(list(c.items()), [('iterable', None)])
self.assertRaises(TypeError, Counter().update, 42)
self.assertRaises(TypeError, Counter().update, {}, {})
self.assertRaises(TypeError, Counter.update)
def test_copying(self): def test_copying(self):
# Check that counters are copyable, deepcopyable, picklable, and # Check that counters are copyable, deepcopyable, picklable, and
#have a repr/eval round-trip #have a repr/eval round-trip
@ -1205,6 +1227,16 @@ class TestCounter(unittest.TestCase):
c.subtract('aaaabbcce') c.subtract('aaaabbcce')
self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1)) self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1))
c = Counter()
c.subtract(self=42)
self.assertEqual(list(c.items()), [('self', -42)])
c = Counter()
c.subtract(iterable=42)
self.assertEqual(list(c.items()), [('iterable', -42)])
self.assertRaises(TypeError, Counter().subtract, 42)
self.assertRaises(TypeError, Counter().subtract, {}, {})
self.assertRaises(TypeError, Counter.subtract)
def test_unary(self): def test_unary(self):
c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40) c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40)
self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40)) self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40))
@ -1255,8 +1287,11 @@ class TestOrderedDict(unittest.TestCase):
c=3, e=5).items()), pairs) # mixed input c=3, e=5).items()), pairs) # mixed input
# make sure no positional args conflict with possible kwdargs # make sure no positional args conflict with possible kwdargs
self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args, self.assertEqual(list(OrderedDict(self=42).items()), [('self', 42)])
['self']) self.assertEqual(list(OrderedDict(other=42).items()), [('other', 42)])
self.assertRaises(TypeError, OrderedDict, 42)
self.assertRaises(TypeError, OrderedDict, (), ())
self.assertRaises(TypeError, OrderedDict.__init__)
# Make sure that direct calls to __init__ do not clear previous contents # Make sure that direct calls to __init__ do not clear previous contents
d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)]) d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
@ -1301,6 +1336,10 @@ class TestOrderedDict(unittest.TestCase):
self.assertEqual(list(d.items()), self.assertEqual(list(d.items()),
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)]) [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
self.assertRaises(TypeError, OrderedDict().update, 42)
self.assertRaises(TypeError, OrderedDict().update, (), ())
self.assertRaises(TypeError, OrderedDict.update)
def test_abc(self): def test_abc(self):
self.assertIsInstance(OrderedDict(), MutableMapping) self.assertIsInstance(OrderedDict(), MutableMapping)
self.assertTrue(issubclass(OrderedDict, MutableMapping)) self.assertTrue(issubclass(OrderedDict, MutableMapping))
@ -1532,6 +1571,24 @@ class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol):
d = self._empty_mapping() d = self._empty_mapping()
self.assertRaises(KeyError, d.popitem) self.assertRaises(KeyError, d.popitem)
class TestUserDict(unittest.TestCase):
def test_init(self):
self.assertEqual(list(UserDict(self=42).items()), [('self', 42)])
self.assertEqual(list(UserDict(dict=42).items()), [('dict', 42)])
self.assertEqual(list(UserDict(dict=None).items()), [('dict', None)])
self.assertRaises(TypeError, UserDict, 42)
self.assertRaises(TypeError, UserDict, (), ())
self.assertRaises(TypeError, UserDict.__init__)
def test_update(self):
d = UserDict()
d.update(self=42)
self.assertEqual(list(d.items()), [('self', 42)])
self.assertRaises(TypeError, UserDict().update, 42)
self.assertRaises(TypeError, UserDict().update, {}, {})
self.assertRaises(TypeError, UserDict.update)
################################################################################ ################################################################################
### Run tests ### Run tests
@ -1543,7 +1600,8 @@ def test_main(verbose=None):
NamedTupleDocs = doctest.DocTestSuite(module=collections) NamedTupleDocs = doctest.DocTestSuite(module=collections)
test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs, test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs,
TestCollectionABCs, TestCounter, TestChainMap, TestCollectionABCs, TestCounter, TestChainMap,
TestOrderedDict, GeneralMappingTests, SubclassMappingTests] TestOrderedDict, GeneralMappingTests, SubclassMappingTests,
TestUserDict,]
support.run_unittest(*test_classes) support.run_unittest(*test_classes)
support.run_doctest(collections, verbose) support.run_doctest(collections, verbose)

View file

@ -36,6 +36,9 @@ Core and Builtins
Library Library
------- -------
- Issue #22609: Constructors and update methods of mapping classes in the
collections module now accept the self keyword argument.
- Issue #22788: Add *context* parameter to logging.handlers.HTTPHandler. - Issue #22788: Add *context* parameter to logging.handlers.HTTPHandler.
- Issue #22921: Allow SSLContext to take the *hostname* parameter even if - Issue #22921: Allow SSLContext to take the *hostname* parameter even if