GeezerVison is showing github.com — bpo-43413: Fix handling keyword arguments in subclasses of some buiti… · python/cpython@92bf869
File treeExpand file treeCollapse file tree Open diff view settings Misc/NEWS.d/next/Core and Builtins
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: Lib/test/seq_tests.py +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -145,6 +145,9 @@ def __getitem__(self, i): | 145 | 145 | self.assertEqual(self.type2test(LyingTuple((2,))), self.type2test((1,))) | 146 | 146 | self.assertEqual(self.type2test(LyingList([2])), self.type2test([1])) | 147 | 147 |
| | 148 | + with self.assertRaises(TypeError): | | 149 | + self.type2test(unsupported_arg=[]) | | 150 | + | 148 | 151 | def test_truth(self): | 149 | 152 | self.assertFalse(self.type2test()) | 150 | 153 | self.assertTrue(self.type2test([42])) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_float.py +27Lines changed: 27 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -245,6 +245,33 @@ def test_keyword_args(self): | 245 | 245 | with self.assertRaisesRegex(TypeError, 'keyword argument'): | 246 | 246 | float(x='3.14') | 247 | 247 |
| | 248 | + def test_keywords_in_subclass(self): | | 249 | + class subclass(float): | | 250 | + pass | | 251 | + u = subclass(2.5) | | 252 | + self.assertIs(type(u), subclass) | | 253 | + self.assertEqual(float(u), 2.5) | | 254 | + with self.assertRaises(TypeError): | | 255 | + subclass(x=0) | | 256 | + | | 257 | + class subclass_with_init(float): | | 258 | + def __init__(self, arg, newarg=None): | | 259 | + self.newarg = newarg | | 260 | + u = subclass_with_init(2.5, newarg=3) | | 261 | + self.assertIs(type(u), subclass_with_init) | | 262 | + self.assertEqual(float(u), 2.5) | | 263 | + self.assertEqual(u.newarg, 3) | | 264 | + | | 265 | + class subclass_with_new(float): | | 266 | + def __new__(cls, arg, newarg=None): | | 267 | + self = super().__new__(cls, arg) | | 268 | + self.newarg = newarg | | 269 | + return self | | 270 | + u = subclass_with_new(2.5, newarg=3) | | 271 | + self.assertIs(type(u), subclass_with_new) | | 272 | + self.assertEqual(float(u), 2.5) | | 273 | + self.assertEqual(u.newarg, 3) | | 274 | + | 248 | 275 | def test_is_integer(self): | 249 | 276 | self.assertFalse((1.1).is_integer()) | 250 | 277 | self.assertTrue((1.).is_integer()) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_itertools.py +51-10Lines changed: 51 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -2229,16 +2229,57 @@ def __eq__(self, other): | 2229 | 2229 | class SubclassWithKwargsTest(unittest.TestCase): | 2230 | 2230 | def test_keywords_in_subclass(self): | 2231 | 2231 | # count is not subclassable... | 2232 | | - for cls in (repeat, zip, filter, filterfalse, chain, map, | 2233 | | - starmap, islice, takewhile, dropwhile, cycle, compress): | 2234 | | - class Subclass(cls): | 2235 | | - def __init__(self, newarg=None, *args): | 2236 | | - cls.__init__(self, *args) | 2237 | | - try: | 2238 | | - Subclass(newarg=1) | 2239 | | - except TypeError as err: | 2240 | | - # we expect type errors because of wrong argument count | 2241 | | - self.assertNotIn("keyword arguments", err.args[0]) | | 2232 | + testcases = [ | | 2233 | + (repeat, (1, 2), [1, 1]), | | 2234 | + (zip, ([1, 2], 'ab'), [(1, 'a'), (2, 'b')]), | | 2235 | + (filter, (None, [0, 1]), [1]), | | 2236 | + (filterfalse, (None, [0, 1]), [0]), | | 2237 | + (chain, ([1, 2], [3, 4]), [1, 2, 3]), | | 2238 | + (map, (str, [1, 2]), ['1', '2']), | | 2239 | + (starmap, (operator.pow, ((2, 3), (3, 2))), [8, 9]), | | 2240 | + (islice, ([1, 2, 3, 4], 1, 3), [2, 3]), | | 2241 | + (takewhile, (isEven, [2, 3, 4]), [2]), | | 2242 | + (dropwhile, (isEven, [2, 3, 4]), [3, 4]), | | 2243 | + (cycle, ([1, 2],), [1, 2, 1]), | | 2244 | + (compress, ('ABC', [1, 0, 1]), ['A', 'C']), | | 2245 | + ] | | 2246 | + for cls, args, result in testcases: | | 2247 | + with self.subTest(cls): | | 2248 | + class subclass(cls): | | 2249 | + pass | | 2250 | + u = subclass(*args) | | 2251 | + self.assertIs(type(u), subclass) | | 2252 | + self.assertEqual(list(islice(u, 0, 3)), result) | | 2253 | + with self.assertRaises(TypeError): | | 2254 | + subclass(*args, newarg=3) | | 2255 | + | | 2256 | + for cls, args, result in testcases: | | 2257 | + # Constructors of repeat, zip, compress accept keyword arguments. | | 2258 | + # Their subclasses need overriding __new__ to support new | | 2259 | + # keyword arguments. | | 2260 | + if cls in [repeat, zip, compress]: | | 2261 | + continue | | 2262 | + with self.subTest(cls): | | 2263 | + class subclass_with_init(cls): | | 2264 | + def __init__(self, *args, newarg=None): | | 2265 | + self.newarg = newarg | | 2266 | + u = subclass_with_init(*args, newarg=3) | | 2267 | + self.assertIs(type(u), subclass_with_init) | | 2268 | + self.assertEqual(list(islice(u, 0, 3)), result) | | 2269 | + self.assertEqual(u.newarg, 3) | | 2270 | + | | 2271 | + for cls, args, result in testcases: | | 2272 | + with self.subTest(cls): | | 2273 | + class subclass_with_new(cls): | | 2274 | + def __new__(cls, *args, newarg=None): | | 2275 | + self = super().__new__(cls, *args) | | 2276 | + self.newarg = newarg | | 2277 | + return self | | 2278 | + u = subclass_with_new(*args, newarg=3) | | 2279 | + self.assertIs(type(u), subclass_with_new) | | 2280 | + self.assertEqual(list(islice(u, 0, 3)), result) | | 2281 | + self.assertEqual(u.newarg, 3) | | 2282 | + | 2242 | 2283 |
| 2243 | 2284 | @support.cpython_only | 2244 | 2285 | class SizeofTest(unittest.TestCase): | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_list.py +28Lines changed: 28 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -46,6 +46,34 @@ def test_keyword_args(self): | 46 | 46 | with self.assertRaisesRegex(TypeError, 'keyword argument'): | 47 | 47 | list(sequence=[]) | 48 | 48 |
| | 49 | + def test_keywords_in_subclass(self): | | 50 | + class subclass(list): | | 51 | + pass | | 52 | + u = subclass([1, 2]) | | 53 | + self.assertIs(type(u), subclass) | | 54 | + self.assertEqual(list(u), [1, 2]) | | 55 | + with self.assertRaises(TypeError): | | 56 | + subclass(sequence=()) | | 57 | + | | 58 | + class subclass_with_init(list): | | 59 | + def __init__(self, seq, newarg=None): | | 60 | + super().__init__(seq) | | 61 | + self.newarg = newarg | | 62 | + u = subclass_with_init([1, 2], newarg=3) | | 63 | + self.assertIs(type(u), subclass_with_init) | | 64 | + self.assertEqual(list(u), [1, 2]) | | 65 | + self.assertEqual(u.newarg, 3) | | 66 | + | | 67 | + class subclass_with_new(list): | | 68 | + def __new__(cls, seq, newarg=None): | | 69 | + self = super().__new__(cls, seq) | | 70 | + self.newarg = newarg | | 71 | + return self | | 72 | + u = subclass_with_new([1, 2], newarg=3) | | 73 | + self.assertIs(type(u), subclass_with_new) | | 74 | + self.assertEqual(list(u), [1, 2]) | | 75 | + self.assertEqual(u.newarg, 3) | | 76 | + | 49 | 77 | def test_truth(self): | 50 | 78 | super().test_truth() | 51 | 79 | self.assertTrue(not []) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_set.py +54-8Lines changed: 54 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -644,15 +644,34 @@ class TestSetSubclass(TestSet): | 644 | 644 | thetype = SetSubclass | 645 | 645 | basetype = set | 646 | 646 |
| 647 | | -class SetSubclassWithKeywordArgs(set): | 648 | | - def __init__(self, iterable=[], newarg=None): | 649 | | - set.__init__(self, iterable) | 650 | | - | 651 | | -class TestSetSubclassWithKeywordArgs(TestSet): | 652 | | - | 653 | 647 | def test_keywords_in_subclass(self): | 654 | | - 'SF bug #1486663 -- this used to erroneously raise a TypeError' | 655 | | - SetSubclassWithKeywordArgs(newarg=1) | | 648 | + class subclass(set): | | 649 | + pass | | 650 | + u = subclass([1, 2]) | | 651 | + self.assertIs(type(u), subclass) | | 652 | + self.assertEqual(set(u), {1, 2}) | | 653 | + with self.assertRaises(TypeError): | | 654 | + subclass(sequence=()) | | 655 | + | | 656 | + class subclass_with_init(set): | | 657 | + def __init__(self, arg, newarg=None): | | 658 | + super().__init__(arg) | | 659 | + self.newarg = newarg | | 660 | + u = subclass_with_init([1, 2], newarg=3) | | 661 | + self.assertIs(type(u), subclass_with_init) | | 662 | + self.assertEqual(set(u), {1, 2}) | | 663 | + self.assertEqual(u.newarg, 3) | | 664 | + | | 665 | + class subclass_with_new(set): | | 666 | + def __new__(cls, arg, newarg=None): | | 667 | + self = super().__new__(cls, arg) | | 668 | + self.newarg = newarg | | 669 | + return self | | 670 | + u = subclass_with_new([1, 2], newarg=3) | | 671 | + self.assertIs(type(u), subclass_with_new) | | 672 | + self.assertEqual(set(u), {1, 2}) | | 673 | + self.assertEqual(u.newarg, 3) | | 674 | + | 656 | 675 |
| 657 | 676 | class TestFrozenSet(TestJointOps, unittest.TestCase): | 658 | 677 | thetype = frozenset | @@ -734,6 +753,33 @@ class TestFrozenSetSubclass(TestFrozenSet): | 734 | 753 | thetype = FrozenSetSubclass | 735 | 754 | basetype = frozenset | 736 | 755 |
| | 756 | + def test_keywords_in_subclass(self): | | 757 | + class subclass(frozenset): | | 758 | + pass | | 759 | + u = subclass([1, 2]) | | 760 | + self.assertIs(type(u), subclass) | | 761 | + self.assertEqual(set(u), {1, 2}) | | 762 | + with self.assertRaises(TypeError): | | 763 | + subclass(sequence=()) | | 764 | + | | 765 | + class subclass_with_init(frozenset): | | 766 | + def __init__(self, arg, newarg=None): | | 767 | + self.newarg = newarg | | 768 | + u = subclass_with_init([1, 2], newarg=3) | | 769 | + self.assertIs(type(u), subclass_with_init) | | 770 | + self.assertEqual(set(u), {1, 2}) | | 771 | + self.assertEqual(u.newarg, 3) | | 772 | + | | 773 | + class subclass_with_new(frozenset): | | 774 | + def __new__(cls, arg, newarg=None): | | 775 | + self = super().__new__(cls, arg) | | 776 | + self.newarg = newarg | | 777 | + return self | | 778 | + u = subclass_with_new([1, 2], newarg=3) | | 779 | + self.assertIs(type(u), subclass_with_new) | | 780 | + self.assertEqual(set(u), {1, 2}) | | 781 | + self.assertEqual(u.newarg, 3) | | 782 | + | 737 | 783 | def test_constructor_identity(self): | 738 | 784 | s = self.thetype(range(3)) | 739 | 785 | t = self.thetype(s) | |
Collapse file Copy file name to clipboardExpand all lines: Lib/test/test_tuple.py +27Lines changed: 27 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -42,6 +42,33 @@ def test_keyword_args(self): | 42 | 42 | with self.assertRaisesRegex(TypeError, 'keyword argument'): | 43 | 43 | tuple(sequence=()) | 44 | 44 |
| | 45 | + def test_keywords_in_subclass(self): | | 46 | + class subclass(tuple): | | 47 | + pass | | 48 | + u = subclass([1, 2]) | | 49 | + self.assertIs(type(u), subclass) | | 50 | + self.assertEqual(list(u), [1, 2]) | | 51 | + with self.assertRaises(TypeError): | | 52 | + subclass(sequence=()) | | 53 | + | | 54 | + class subclass_with_init(tuple): | | 55 | + def __init__(self, arg, newarg=None): | | 56 | + self.newarg = newarg | | 57 | + u = subclass_with_init([1, 2], newarg=3) | | 58 | + self.assertIs(type(u), subclass_with_init) | | 59 | + self.assertEqual(list(u), [1, 2]) | | 60 | + self.assertEqual(u.newarg, 3) | | 61 | + | | 62 | + class subclass_with_new(tuple): | | 63 | + def __new__(cls, arg, newarg=None): | | 64 | + self = super().__new__(cls, arg) | | 65 | + self.newarg = newarg | | 66 | + return self | | 67 | + u = subclass_with_new([1, 2], newarg=3) | | 68 | + self.assertIs(type(u), subclass_with_new) | | 69 | + self.assertEqual(list(u), [1, 2]) | | 70 | + self.assertEqual(u.newarg, 3) | | 71 | + | 45 | 72 | def test_truth(self): | 46 | 73 | super().test_truth() | 47 | 74 | self.assertTrue(not ()) | |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Constructors of subclasses of some buitin classes (e.g. :class:`tuple`, | | 2 | +:class:`list`, :class:`frozenset`) no longer accept arbitrary keyword | | 3 | +arguments. Subclass of :class:`set` can now define a ``__new__()`` method | | 4 | +with additional keyword parameters without overriding also ``__init__()``. |
Collapse file Copy file name to clipboardExpand all lines: Modules/_io/clinic/bufferedio.c.h +3-2Lines changed: 3 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -553,7 +553,8 @@ _io_BufferedRWPair___init__(PyObject *self, PyObject *args, PyObject *kwargs) | 553 | 553 | PyObject *writer; | 554 | 554 | Py_ssize_t buffer_size = DEFAULT_BUFFER_SIZE; | 555 | 555 |
| 556 | | - if (Py_IS_TYPE(self, &PyBufferedRWPair_Type) && | | 556 | + if ((Py_IS_TYPE(self, &PyBufferedRWPair_Type) || | | 557 | + Py_TYPE(self)->tp_new == PyBufferedRWPair_Type.tp_new) && | 557 | 558 | !_PyArg_NoKeywords("BufferedRWPair", kwargs)) { | 558 | 559 | goto exit; | 559 | 560 | } | @@ -637,4 +638,4 @@ _io_BufferedRandom___init__(PyObject *self, PyObject *args, PyObject *kwargs) | 637 | 638 | exit: | 638 | 639 | return return_value; | 639 | 640 | } | 640 | | -/*[clinic end generated code: output=98ccf7610c0e82ba input=a9049054013a1b77]*/ | | 641 | +/*[clinic end generated code: output=79138a088729b5ee input=a9049054013a1b77]*/ |
Collapse file Copy file name to clipboardExpand all lines: Modules/_randommodule.c +3-2Lines changed: 3 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -523,8 +523,9 @@ random_init(RandomObject *self, PyObject *args, PyObject *kwds) | 523 | 523 | PyObject *arg = NULL; | 524 | 524 | _randomstate *state = _randomstate_type(Py_TYPE(self)); | 525 | 525 |
| 526 | | - if (Py_IS_TYPE(self, (PyTypeObject *)state->Random_Type) && | 527 | | - !_PyArg_NoKeywords("Random()", kwds)) { | | 526 | + if ((Py_IS_TYPE(self, (PyTypeObject *)state->Random_Type) || | | 527 | + Py_TYPE(self)->tp_init == ((PyTypeObject*)state->Random_Type)->tp_init) && | | 528 | + !_PyArg_NoKeywords("Random", kwds)) { | 528 | 529 | return -1; | 529 | 530 | } | 530 | 531 |
| |
Collapse file Copy file name to clipboardExpand all lines: Modules/_sqlite/clinic/cursor.c.h +3-2Lines changed: 3 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -12,7 +12,8 @@ pysqlite_cursor_init(PyObject *self, PyObject *args, PyObject *kwargs) | 12 | 12 | int return_value = -1; | 13 | 13 | pysqlite_Connection *connection; | 14 | 14 |
| 15 | | - if (Py_IS_TYPE(self, clinic_state()->CursorType) && | | 15 | + if ((Py_IS_TYPE(self, clinic_state()->CursorType) || | | 16 | + Py_TYPE(self)->tp_new == clinic_state()->CursorType->tp_new) && | 16 | 17 | !_PyArg_NoKeywords("Cursor", kwargs)) { | 17 | 18 | goto exit; | 18 | 19 | } | @@ -299,4 +300,4 @@ pysqlite_cursor_close(pysqlite_Cursor *self, PyTypeObject *cls, PyObject *const | 299 | 300 | exit: | 300 | 301 | return return_value; | 301 | 302 | } | 302 | | -/*[clinic end generated code: output=ace31a7481aa3f41 input=a9049054013a1b77]*/ | | 303 | +/*[clinic end generated code: output=3b5328c1619b7626 input=a9049054013a1b77]*/ |
|