|
9 | 9 | import pickle |
10 | 10 | import inspect |
11 | 11 | import builtins |
| 12 | +import re |
12 | 13 | import types |
13 | 14 | import weakref |
14 | 15 | import traceback |
|
18 | 19 | from typing import get_type_hints |
19 | 20 | from collections import deque, OrderedDict, namedtuple, defaultdict |
20 | 21 | from functools import total_ordering |
| 22 | +from itertools import product |
21 | 23 |
|
22 | 24 | import typing # Needed for the string "typing.ClassVar[int]" to work as an annotation. |
23 | 25 | import dataclasses # Needed for the string "dataclasses.InitVar[int]" to work as an annotation. |
@@ -1411,6 +1413,61 @@ class C: |
1411 | 1413 | C().x |
1412 | 1414 | self.assertEqual(factory.call_count, 2) |
1413 | 1415 |
|
| 1416 | + def test_default_factory_with_no_init_method(self): |
| 1417 | + # See https://github.com/python/cpython/issues/89529. |
| 1418 | + |
| 1419 | + @dataclass |
| 1420 | + class BaseWithInit: |
| 1421 | + x: list |
| 1422 | + |
| 1423 | + @dataclass(slots=True) |
| 1424 | + class BaseWithSlots: |
| 1425 | + x: list |
| 1426 | + |
| 1427 | + @dataclass(init=False) |
| 1428 | + class BaseWithOutInit: |
| 1429 | + x: list |
| 1430 | + |
| 1431 | + @dataclass(init=False, slots=True) |
| 1432 | + class BaseWithOutInitWithSlots: |
| 1433 | + x: list |
| 1434 | + |
| 1435 | + err = re.escape( |
| 1436 | + "specifying default_factory for 'x' requires the " |
| 1437 | + "@dataclass decorator to be called with init=True " |
| 1438 | + "or to implement an __init__ method" |
| 1439 | + ) |
| 1440 | + |
| 1441 | + for base_class, slots, field_init in product( |
| 1442 | + (object, BaseWithInit, BaseWithSlots, |
| 1443 | + BaseWithOutInit, BaseWithOutInitWithSlots), |
| 1444 | + (True, False), |
| 1445 | + (True, False), |
| 1446 | + ): |
| 1447 | + with self.subTest('generated __init__', base_class=base_class, |
| 1448 | + init=True, slots=slots, field_init=field_init): |
| 1449 | + @dataclass(init=True, slots=slots) |
| 1450 | + class C(base_class): |
| 1451 | + x: list = field(init=field_init, default_factory=list) |
| 1452 | + self.assertListEqual(C().x, []) |
| 1453 | + |
| 1454 | + with self.subTest('user-defined __init__', base_class=base_class, |
| 1455 | + init=False, slots=slots, field_init=field_init): |
| 1456 | + @dataclass(init=False, slots=slots) |
| 1457 | + class C(base_class): |
| 1458 | + x: list = field(init=field_init, default_factory=list) |
| 1459 | + def __init__(self, *a, **kw): |
| 1460 | + # deliberately use something else |
| 1461 | + self.x = 'hello' |
| 1462 | + self.assertEqual(C().x, 'hello') |
| 1463 | + |
| 1464 | + with self.subTest('no generated __init__', base_class=base_class, |
| 1465 | + init=False, slots=slots, field_init=field_init): |
| 1466 | + with self.assertRaisesRegex(ValueError, err): |
| 1467 | + @dataclass(init=False, slots=slots) |
| 1468 | + class C(base_class): |
| 1469 | + x: list = field(init=field_init, default_factory=list) |
| 1470 | + |
1414 | 1471 | def test_default_factory_not_called_if_value_given(self): |
1415 | 1472 | # We need a factory that we can test if it's been called. |
1416 | 1473 | factory = Mock() |
|
0 commit comments