Skip to content

Commit ae5cb21

Browse files
Issue #22609: Constructors and update methods of mapping classes in the
collections module now accept the self keyword argument.
1 parent 4847035 commit ae5cb21

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
@@ -584,23 +584,24 @@ def update(*args, **kwds):
584584
If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v
585585
In either case, this is followed by: for k, v in F.items(): D[k] = v
586586
'''
587-
if len(args) > 2:
588-
raise TypeError("update() takes at most 2 positional "
589-
"arguments ({} given)".format(len(args)))
590-
elif not args:
591-
raise TypeError("update() takes at least 1 argument (0 given)")
592-
self = args[0]
593-
other = args[1] if len(args) >= 2 else ()
594-
595-
if isinstance(other, Mapping):
596-
for key in other:
597-
self[key] = other[key]
598-
elif hasattr(other, "keys"):
599-
for key in other.keys():
600-
self[key] = other[key]
601-
else:
602-
for key, value in other:
603-
self[key] = value
587+
if not args:
588+
raise TypeError("descriptor 'update' of 'MutableMapping' object "
589+
"needs an argument")
590+
self, *args = args
591+
if len(args) > 1:
592+
raise TypeError('update expected at most 1 arguments, got %d' %
593+
len(args))
594+
if args:
595+
other = args[0]
596+
if isinstance(other, Mapping):
597+
for key in other:
598+
self[key] = other[key]
599+
elif hasattr(other, "keys"):
600+
for key in other.keys():
601+
self[key] = other[key]
602+
else:
603+
for key, value in other:
604+
self[key] = value
604605
for key, value in kwds.items():
605606
self[key] = value
606607

‎Lib/collections/__init__.py‎

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

41-
def __init__(self, *args, **kwds):
41+
def __init__(*args, **kwds):
4242
'''Initialize an ordered dictionary. The signature is the same as
4343
regular dictionaries, but keyword arguments are not recommended because
4444
their insertion order is arbitrary.
4545
4646
'''
47+
if not args:
48+
raise TypeError("descriptor '__init__' of 'OrderedDict' object "
49+
"needs an argument")
50+
self, *args = args
4751
if len(args) > 1:
4852
raise TypeError('expected at most 1 arguments, got %d' % len(args))
4953
try:
@@ -450,7 +454,7 @@ class Counter(dict):
450454
# http://code.activestate.com/recipes/259174/
451455
# Knuth, TAOCP Vol. II section 4.6.3
452456

453-
def __init__(self, iterable=None, **kwds):
457+
def __init__(*args, **kwds):
454458
'''Create a new, empty Counter object. And if given, count elements
455459
from an input iterable. Or, initialize the count from another mapping
456460
of elements to their counts.
@@ -461,8 +465,14 @@ def __init__(self, iterable=None, **kwds):
461465
>>> c = Counter(a=4, b=2) # a new counter from keyword args
462466
463467
'''
464-
super().__init__()
465-
self.update(iterable, **kwds)
468+
if not args:
469+
raise TypeError("descriptor '__init__' of 'Counter' object "
470+
"needs an argument")
471+
self, *args = args
472+
if len(args) > 1:
473+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
474+
super(Counter, self).__init__()
475+
self.update(*args, **kwds)
466476

467477
def __missing__(self, key):
468478
'The count of elements not in the Counter is zero.'
@@ -513,7 +523,7 @@ def fromkeys(cls, iterable, v=None):
513523
raise NotImplementedError(
514524
'Counter.fromkeys() is undefined. Use Counter(iterable) instead.')
515525

516-
def update(self, iterable=None, **kwds):
526+
def update(*args, **kwds):
517527
'''Like dict.update() but add counts instead of replacing them.
518528
519529
Source can be an iterable, a dictionary, or another Counter instance.
@@ -533,20 +543,27 @@ def update(self, iterable=None, **kwds):
533543
# contexts. Instead, we implement straight-addition. Both the inputs
534544
# and outputs are allowed to contain zero and negative counts.
535545

546+
if not args:
547+
raise TypeError("descriptor 'update' of 'Counter' object "
548+
"needs an argument")
549+
self, *args = args
550+
if len(args) > 1:
551+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
552+
iterable = args[0] if args else None
536553
if iterable is not None:
537554
if isinstance(iterable, Mapping):
538555
if self:
539556
self_get = self.get
540557
for elem, count in iterable.items():
541558
self[elem] = count + self_get(elem, 0)
542559
else:
543-
super().update(iterable) # fast path when counter is empty
560+
super(Counter, self).update(iterable) # fast path when counter is empty
544561
else:
545562
_count_elements(self, iterable)
546563
if kwds:
547564
self.update(kwds)
548565

549-
def subtract(self, iterable=None, **kwds):
566+
def subtract(*args, **kwds):
550567
'''Like dict.update() but subtracts counts instead of replacing them.
551568
Counts can be reduced below zero. Both the inputs and outputs are
552569
allowed to contain zero and negative counts.
@@ -562,6 +579,13 @@ def subtract(self, iterable=None, **kwds):
562579
-1
563580
564581
'''
582+
if not args:
583+
raise TypeError("descriptor 'subtract' of 'Counter' object "
584+
"needs an argument")
585+
self, *args = args
586+
if len(args) > 1:
587+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
588+
iterable = args[0] if args else None
565589
if iterable is not None:
566590
self_get = self.get
567591
if isinstance(iterable, Mapping):
@@ -869,7 +893,14 @@ def clear(self):
869893
class UserDict(MutableMapping):
870894

871895
# Start by filling-out the abstract methods
872-
def __init__(self, dict=None, **kwargs):
896+
def __init__(*args, **kwargs):
897+
if not args:
898+
raise TypeError("descriptor '__init__' of 'UserDict' object "
899+
"needs an argument")
900+
self, *args = args
901+
if len(args) > 1:
902+
raise TypeError('expected at most 1 arguments, got %d' % len(args))
903+
dict = args[0] if args else None
873904
self.data = {}
874905
if dict is not None:
875906
self.update(dict)

‎Lib/test/test_collections.py‎

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

1087+
def test_init(self):
1088+
self.assertEqual(list(Counter(self=42).items()), [('self', 42)])
1089+
self.assertEqual(list(Counter(iterable=42).items()), [('iterable', 42)])
1090+
self.assertEqual(list(Counter(iterable=None).items()), [('iterable', None)])
1091+
self.assertRaises(TypeError, Counter, 42)
1092+
self.assertRaises(TypeError, Counter, (), ())
1093+
self.assertRaises(TypeError, Counter.__init__)
1094+
1095+
def test_update(self):
1096+
c = Counter()
1097+
c.update(self=42)
1098+
self.assertEqual(list(c.items()), [('self', 42)])
1099+
c = Counter()
1100+
c.update(iterable=42)
1101+
self.assertEqual(list(c.items()), [('iterable', 42)])
1102+
c = Counter()
1103+
c.update(iterable=None)
1104+
self.assertEqual(list(c.items()), [('iterable', None)])
1105+
self.assertRaises(TypeError, Counter().update, 42)
1106+
self.assertRaises(TypeError, Counter().update, {}, {})
1107+
self.assertRaises(TypeError, Counter.update)
1108+
10871109
def test_copying(self):
10881110
# Check that counters are copyable, deepcopyable, picklable, and
10891111
#have a repr/eval round-trip
@@ -1205,6 +1227,16 @@ def test_subtract(self):
12051227
c.subtract('aaaabbcce')
12061228
self.assertEqual(c, Counter(a=-1, b=0, c=-1, d=1, e=-1))
12071229

1230+
c = Counter()
1231+
c.subtract(self=42)
1232+
self.assertEqual(list(c.items()), [('self', -42)])
1233+
c = Counter()
1234+
c.subtract(iterable=42)
1235+
self.assertEqual(list(c.items()), [('iterable', -42)])
1236+
self.assertRaises(TypeError, Counter().subtract, 42)
1237+
self.assertRaises(TypeError, Counter().subtract, {}, {})
1238+
self.assertRaises(TypeError, Counter.subtract)
1239+
12081240
def test_unary(self):
12091241
c = Counter(a=-5, b=0, c=5, d=10, e=15,g=40)
12101242
self.assertEqual(dict(+c), dict(c=5, d=10, e=15, g=40))
@@ -1255,8 +1287,11 @@ def test_init(self):
12551287
c=3, e=5).items()), pairs) # mixed input
12561288

12571289
# make sure no positional args conflict with possible kwdargs
1258-
self.assertEqual(inspect.getargspec(OrderedDict.__dict__['__init__']).args,
1259-
['self'])
1290+
self.assertEqual(list(OrderedDict(self=42).items()), [('self', 42)])
1291+
self.assertEqual(list(OrderedDict(other=42).items()), [('other', 42)])
1292+
self.assertRaises(TypeError, OrderedDict, 42)
1293+
self.assertRaises(TypeError, OrderedDict, (), ())
1294+
self.assertRaises(TypeError, OrderedDict.__init__)
12601295

12611296
# Make sure that direct calls to __init__ do not clear previous contents
12621297
d = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 44), ('e', 55)])
@@ -1301,6 +1336,10 @@ def test_update(self):
13011336
self.assertEqual(list(d.items()),
13021337
[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7)])
13031338

1339+
self.assertRaises(TypeError, OrderedDict().update, 42)
1340+
self.assertRaises(TypeError, OrderedDict().update, (), ())
1341+
self.assertRaises(TypeError, OrderedDict.update)
1342+
13041343
def test_abc(self):
13051344
self.assertIsInstance(OrderedDict(), MutableMapping)
13061345
self.assertTrue(issubclass(OrderedDict, MutableMapping))
@@ -1532,6 +1571,24 @@ def test_popitem(self):
15321571
d = self._empty_mapping()
15331572
self.assertRaises(KeyError, d.popitem)
15341573

1574+
class TestUserDict(unittest.TestCase):
1575+
1576+
def test_init(self):
1577+
self.assertEqual(list(UserDict(self=42).items()), [('self', 42)])
1578+
self.assertEqual(list(UserDict(dict=42).items()), [('dict', 42)])
1579+
self.assertEqual(list(UserDict(dict=None).items()), [('dict', None)])
1580+
self.assertRaises(TypeError, UserDict, 42)
1581+
self.assertRaises(TypeError, UserDict, (), ())
1582+
self.assertRaises(TypeError, UserDict.__init__)
1583+
1584+
def test_update(self):
1585+
d = UserDict()
1586+
d.update(self=42)
1587+
self.assertEqual(list(d.items()), [('self', 42)])
1588+
self.assertRaises(TypeError, UserDict().update, 42)
1589+
self.assertRaises(TypeError, UserDict().update, {}, {})
1590+
self.assertRaises(TypeError, UserDict.update)
1591+
15351592

15361593
################################################################################
15371594
### Run tests
@@ -1543,7 +1600,8 @@ def test_main(verbose=None):
15431600
NamedTupleDocs = doctest.DocTestSuite(module=collections)
15441601
test_classes = [TestNamedTuple, NamedTupleDocs, TestOneTrickPonyABCs,
15451602
TestCollectionABCs, TestCounter, TestChainMap,
1546-
TestOrderedDict, GeneralMappingTests, SubclassMappingTests]
1603+
TestOrderedDict, GeneralMappingTests, SubclassMappingTests,
1604+
TestUserDict,]
15471605
support.run_unittest(*test_classes)
15481606
support.run_doctest(collections, verbose)
15491607

‎Misc/NEWS‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ Core and Builtins
3636
Library
3737
-------
3838

39+
- Issue #22609: Constructors and update methods of mapping classes in the
40+
collections module now accept the self keyword argument.
41+
3942
- Issue #22788: Add *context* parameter to logging.handlers.HTTPHandler.
4043

4144
- Issue #22921: Allow SSLContext to take the *hostname* parameter even if

0 commit comments

Comments
 (0)