Skip to content

Commit 8943ecf

Browse files
Issue #22609: Constructors and update methods of mapping classes in the
collections module now accept the self keyword argument.
2 parents f25e3bf + ae5cb21 commit 8943ecf

4 files changed

Lines changed: 121 additions & 28 deletions

File tree

‎Lib/_collections_abc.py‎

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -592,23 +592,24 @@ def update(*args, **kwds):
592592
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
593593
In either case, this is followed by: for k, v in F.items(): D[k] = v
594594
'''
595-
if len(args) > 2:
596-
raise TypeError("update() takes at most 2 positional "
597-
"arguments ({} given)".format(len(args)))
598-
elif not args:
599-
raise TypeError("update() takes at least 1 argument (0 given)")
600-
self = args[0]
601-
other = args[1] if len(args) >= 2 else ()
602-
603-
if isinstance(other, Mapping):
604-
for key in other:
605-
self[key] = other[key]
606-
elif hasattr(other, "keys"):
607-
for key in other.keys():
608-
self[key] = other[key]
609-
else:
610-
for key, value in other:
611-
self[key] = value
595+
if not args:
596+
raise TypeError("descriptor 'update' of 'MutableMapping' object "
597+
"needs an argument")
598+
self, *args = args
599+
if len(args) > 1:
600+
raise TypeError('update expected at most 1 arguments, got %d' %
601+
len(args))
602+
if args:
603+
other = args[0]
604+
if isinstance(other, Mapping):
605+
for key in other:
606+
self[key] = other[key]
607+
elif hasattr(other, "keys"):
608+
for key in other.keys():
609+
self[key] = other[key]
610+
else:
611+
for key, value in other:
612+
self[key] = value
612613
for key, value in kwds.items():
613614
self[key] = value
614615

‎Lib/collections/__init__.py‎

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ class OrderedDict(dict):
5555
# Individual links are kept alive by the hard reference in self.__map.
5656
# Those hard references disappear when a key is deleted from an OrderedDict.
5757

58-
def __init__(self, *args, **kwds):
58+
def __init__(*args, **kwds):
5959
'''Initialize an ordered dictionary. The signature is the same as
6060
regular dictionaries, but keyword arguments are not recommended because
6161
their insertion order is arbitrary.
6262
6363
'''
64+
if not args:
65+
raise TypeError("descriptor '__init__' of 'OrderedDict' object "
66+
"needs an argument")
67+
self, *args = args
6468
if len(args) > 1:
6569
raise TypeError('expected at most 1 arguments, got %d' % len(args))
6670
try:
@@ -479,7 +483,7 @@ class Counter(dict):
479483
# http://code.activestate.com/recipes/259174/
480484
# Knuth, TAOCP Vol. II section 4.6.3
481485

482-
def __init__(self, iterable=None, **kwds):
486+
def __init__(*args, **kwds):
483487
'''Create a new, empty Counter object. And if given, count elements
484488
from an input iterable. Or, initialize the count from another mapping
485489
of elements to their counts.
@@ -490,8 +494,14 @@ def __init__(self, iterable=None, **kwds):
490494
>>> c = Counter(a=4, b=2) # a new counter from keyword args
491495
492496
'''
493-
super().__init__()
494-
self.update(iterable, **kwds)
497+
if not args:
498+
raise TypeError("descriptor '__init__' of 'Counter' object "
499+
"needs an argument")
500+
self, *args = args
501+
if len(args) > 1:
502+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
503+
super(Counter, self).__init__()
504+
self.update(*args, **kwds)
495505

496506
def __missing__(self, key):
497507
'The count of elements not in the Counter is zero.'
@@ -542,7 +552,7 @@ def fromkeys(cls, iterable, v=None):
542552
raise NotImplementedError(
543553
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
544554

545-
def update(self, iterable=None, **kwds):
555+
def update(*args, **kwds):
546556
'''Like dict.update() but add counts instead of replacing them.
547557
548558
Source can be an iterable, a dictionary, or another Counter instance.
@@ -562,20 +572,27 @@ def update(self, iterable=None, **kwds):
562572
# contexts. Instead, we implement straight-addition. Both the inputs
563573
# and outputs are allowed to contain zero and negative counts.
564574

575+
if not args:
576+
raise TypeError("descriptor 'update' of 'Counter' object "
577+
"needs an argument")
578+
self, *args = args
579+
if len(args) > 1:
580+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
581+
iterable = args[0] if args else None
565582
if iterable is not None:
566583
if isinstance(iterable, Mapping):
567584
if self:
568585
self_get = self.get
569586
for elem, count in iterable.items():
570587
self[elem] = count + self_get(elem, 0)
571588
else:
572-
super().update(iterable) # fast path when counter is empty
589+
super(Counter, self).update(iterable) # fast path when counter is empty
573590
else:
574591
_count_elements(self, iterable)
575592
if kwds:
576593
self.update(kwds)
577594

578-
def subtract(self, iterable=None, **kwds):
595+
def subtract(*args, **kwds):
579596
'''Like dict.update() but subtracts counts instead of replacing them.
580597
Counts can be reduced below zero. Both the inputs and outputs are
581598
allowed to contain zero and negative counts.
@@ -591,6 +608,13 @@ def subtract(self, iterable=None, **kwds):
591608
-1
592609
593610
'''
611+
if not args:
612+
raise TypeError("descriptor 'subtract' of 'Counter' object "
613+
"needs an argument")
614+
self, *args = args
615+
if len(args) > 1:
616+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
617+
iterable = args[0] if args else None
594618
if iterable is not None:
595619
self_get = self.get
596620
if isinstance(iterable, Mapping):
@@ -898,7 +922,14 @@ def clear(self):
898922
class UserDict(MutableMapping):
899923

900924
# Start by filling-out the abstract methods
901-
def __init__(self, dict=None, **kwargs):
925+
def __init__(*args, **kwargs):
926+
if not args:
927+
raise TypeError("descriptor '__init__' of 'UserDict' object "
928+
"needs an argument")
929+
self, *args = args
930+
if len(args) > 1:
931+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
932+
dict = args[0] if args else None
902933
self.data = {}
903934
if dict is not None:
904935
self.update(dict)

‎Lib/test/test_collections.py‎

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,28 @@ def test_basics(self):
11371137
self.assertEqual(c.setdefault('e', 5), 5)
11381138
self.assertEqual(c['e'], 5)
11391139

1140+
def test_init(self):
1141+
self.assertEqual(list(Counter(self=42).items()), [('self', 42)])
1142+
self.assertEqual(list(Counter(iterable=42).items()), [('iterable', 42)])
1143+
self.assertEqual(list(Counter(iterable=None).items()), [('iterable', None)])
1144+
self.assertRaises(TypeError, Counter, 42)
1145+
self.assertRaises(TypeError, Counter, (), ())
1146+
self.assertRaises(TypeError, Counter.__init__)
1147+
1148+
def test_update(self):
1149+
c = Counter()
1150+
c.update(self=42)
1151+
self.assertEqual(list(c.items()), [('self', 42)])
1152+
c = Counter()
1153+
c.update(iterable=42)
1154+
self.assertEqual(list(c.items()), [('iterable', 42)])
1155+
c = Counter()
1156+
c.update(iterable=None)
1157+
self.assertEqual(list(c.items()), [('iterable', None)])
1158+
self.assertRaises(TypeError, Counter().update, 42)
1159+
self.assertRaises(TypeError, Counter().update, {}, {})
1160+
self.assertRaises(TypeError, Counter.update)
1161+
11401162
def test_copying(self):
11411163
# Check that counters are copyable, deepcopyable, picklable, and
11421164
#have a repr/eval round-trip
@@ -1258,6 +1280,16 @@ def test_subtract(self):
12581280
c.subtract('aaaabbcce')
12591281
self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1))
12601282

1283+
c = Counter()
1284+
c.subtract(self=42)
1285+
self.assertEqual(list(c.items()), [('self', -42)])
1286+
c = Counter()
1287+
c.subtract(iterable=42)
1288+
self.assertEqual(list(c.items()), [('iterable', -42)])
1289+
self.assertRaises(TypeError, Counter().subtract, 42)
1290+
self.assertRaises(TypeError, Counter().subtract, {}, {})
1291+
self.assertRaises(TypeError, Counter.subtract)
1292+
12611293
def test_unary(self):
12621294
c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40)
12631295
self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40))
@@ -1308,8 +1340,11 @@ def test_init(self):
13081340
c=3, e=5).items()), pairs) # mixed input
13091341

13101342
# make sure no positional args conflict with possible kwdargs
1311-
self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args,
1312-
['self'])
1343+
self.assertEqual(list(OrderedDict(self=42).items()), [('self', 42)])
1344+
self.assertEqual(list(OrderedDict(other=42).items()), [('other', 42)])
1345+
self.assertRaises(TypeError, OrderedDict, 42)
1346+
self.assertRaises(TypeError, OrderedDict, (), ())
1347+
self.assertRaises(TypeError, OrderedDict.__init__)
13131348

13141349
# Make sure that direct calls to __init__ do not clear previous contents
13151350
d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
@@ -1354,6 +1389,10 @@ def test_update(self):
13541389
self.assertEqual(list(d.items()),
13551390
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
13561391

1392+
self.assertRaises(TypeError, OrderedDict().update, 42)
1393+
self.assertRaises(TypeError, OrderedDict().update, (), ())
1394+
self.assertRaises(TypeError, OrderedDict.update)
1395+
13571396
def test_abc(self):
13581397
self.assertIsInstance(OrderedDict(), MutableMapping)
13591398
self.assertTrue(issubclass(OrderedDict, MutableMapping))
@@ -1600,6 +1639,24 @@ def test_popitem(self):
16001639
d = self._empty_mapping()
16011640
self.assertRaises(KeyError, d.popitem)
16021641

1642+
class TestUserDict(unittest.TestCase):
1643+
1644+
def test_init(self):
1645+
self.assertEqual(list(UserDict(self=42).items()), [('self', 42)])
1646+
self.assertEqual(list(UserDict(dict=42).items()), [('dict', 42)])
1647+
self.assertEqual(list(UserDict(dict=None).items()), [('dict', None)])
1648+
self.assertRaises(TypeError, UserDict, 42)
1649+
self.assertRaises(TypeError, UserDict, (), ())
1650+
self.assertRaises(TypeError, UserDict.__init__)
1651+
1652+
def test_update(self):
1653+
d = UserDict()
1654+
d.update(self=42)
1655+
self.assertEqual(list(d.items()), [('self', 42)])
1656+
self.assertRaises(TypeError, UserDict().update, 42)
1657+
self.assertRaises(TypeError, UserDict().update, {}, {})
1658+
self.assertRaises(TypeError, UserDict.update)
1659+
16031660

16041661
################################################################################
16051662
### Run tests
@@ -1611,7 +1668,8 @@ def test_main(verbose=None):
16111668
NamedTupleDocs = doctest.DocTestSuite(module=collections)
16121669
test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs,
16131670
TestCollectionABCs, TestCounter, TestChainMap,
1614-
TestOrderedDict, GeneralMappingTests, SubclassMappingTests]
1671+
TestOrderedDict, GeneralMappingTests, SubclassMappingTests,
1672+
TestUserDict,]
16151673
support.run_unittest(*test_classes)
16161674
support.run_doctest(collections, verbose)
16171675

‎Misc/NEWS‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ Core and Builtins
191191
Library
192192
-------
193193

194+
- Issue #22609: Constructors and update methods of mapping classes in the
195+
collections module now accept the self keyword argument.
196+
194197
- Issue #22940: Add readline.append_history_file.
195198

196199
- Issue #19676: Added the "namereplace" error handler.

0 commit comments

Comments
 (0)