diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2014-11-27 14:25:51 (GMT) |
---|---|---|
committer | Serhiy Storchaka <storchaka@gmail.com> | 2014-11-27 14:25:51 (GMT) |
commit | ae5cb214d2cd41d96943a0ef43a4e95bd9a10b7a (patch) | |
tree | 9c4dc40180deb531fe0aa1ddfdc8f98bdeece704 | |
parent | 48470354588ced3f635ad15cc77490f0d4536ab3 (diff) | |
download | cpython-ae5cb214d2cd41d96943a0ef43a4e95bd9a10b7a.zip cpython-ae5cb214d2cd41d96943a0ef43a4e95bd9a10b7a.tar.gz cpython-ae5cb214d2cd41d96943a0ef43a4e95bd9a10b7a.tar.bz2 |
Issue #22609: Constructors and update methods of mapping classes in the
collections module now accept the self keyword argument.
-rw-r--r-- | Lib/_collections_abc.py | 35 | ||||
-rw-r--r-- | Lib/collections/__init__.py | 47 | ||||
-rw-r--r-- | Lib/test/test_collections.py | 64 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
4 files changed, 121 insertions, 28 deletions
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 6477444..294a7b1 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -584,23 +584,24 @@ class MutableMapping(Mapping): 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 ''' - if len(args) > 2: - raise TypeError("update() takes at most 2 positional " - "arguments ({} given)".format(len(args))) - elif not args: - raise TypeError("update() takes at least 1 argument (0 given)") - self = args[0] - other = args[1] if len(args) >= 2 else () - - if isinstance(other, Mapping): - for key in other: - self[key] = other[key] - elif hasattr(other, "keys"): - for key in other.keys(): - self[key] = other[key] - else: - for key, value in other: - self[key] = value + if not args: + raise TypeError("descriptor 'update' of 'MutableMapping' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('update expected at most 1 arguments, got %d' % + len(args)) + if args: + other = args[0] + if isinstance(other, Mapping): + for key in other: + self[key] = other[key] + elif hasattr(other, "keys"): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value for key, value in kwds.items(): self[key] = value diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index d993fe0..25d33e0 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -38,12 +38,16 @@ class OrderedDict(dict): # Individual links are kept alive by the hard reference in self.__map. # 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 regular dictionaries, but keyword arguments are not recommended because 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: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: @@ -450,7 +454,7 @@ class Counter(dict): # http://code.activestate.com/recipes/259174/ # 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 from an input iterable. Or, initialize the count from another mapping of elements to their counts. @@ -461,8 +465,14 @@ class Counter(dict): >>> c = Counter(a=4, b=2) # a new counter from keyword args ''' - super().__init__() - self.update(iterable, **kwds) + if not args: + 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): 'The count of elements not in the Counter is zero.' @@ -513,7 +523,7 @@ class Counter(dict): raise NotImplementedError( '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. 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 # 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 isinstance(iterable, Mapping): if self: @@ -540,13 +557,13 @@ class Counter(dict): for elem, count in iterable.items(): self[elem] = count + self_get(elem, 0) else: - super().update(iterable) # fast path when counter is empty + super(Counter, self).update(iterable) # fast path when counter is empty else: _count_elements(self, iterable) if kwds: self.update(kwds) - def subtract(self, iterable=None, **kwds): + def subtract(*args, **kwds): '''Like dict.update() but subtracts counts instead of replacing them. Counts can be reduced below zero. Both the inputs and outputs are allowed to contain zero and negative counts. @@ -562,6 +579,13 @@ class Counter(dict): -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: self_get = self.get if isinstance(iterable, Mapping): @@ -869,7 +893,14 @@ class ChainMap(MutableMapping): class UserDict(MutableMapping): # 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 = {} if dict is not None: self.update(dict) diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index 6407b6f..7780c48 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -1084,6 +1084,28 @@ class TestCounter(unittest.TestCase): self.assertEqual(c.setdefault('e', 5), 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): # Check that counters are copyable, deepcopyable, picklable, and #have a repr/eval round-trip @@ -1205,6 +1227,16 @@ class TestCounter(unittest.TestCase): c.subtract('aaaabbcce') 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): 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)) @@ -1255,8 +1287,11 @@ class TestOrderedDict(unittest.TestCase): c=3, e=5).items()), pairs) # mixed input # make sure no positional args conflict with possible kwdargs - self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args, - ['self']) + self.assertEqual(list(OrderedDict(self=42).items()), [('self', 42)]) + 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 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()), [('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): self.assertIsInstance(OrderedDict(), MutableMapping) self.assertTrue(issubclass(OrderedDict, MutableMapping)) @@ -1532,6 +1571,24 @@ class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol): d = self._empty_mapping() 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 @@ -1543,7 +1600,8 @@ def test_main(verbose=None): NamedTupleDocs = doctest.DocTestSuite(module=collections) test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs, TestCollectionABCs, TestCounter, TestChainMap, - TestOrderedDict, GeneralMappingTests, SubclassMappingTests] + TestOrderedDict, GeneralMappingTests, SubclassMappingTests, + TestUserDict,] support.run_unittest(*test_classes) support.run_doctest(collections, verbose) @@ -36,6 +36,9 @@ Core and Builtins 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 #22921: Allow SSLContext to take the *hostname* parameter even if |