Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b48be8f

Browse files
authored
gh-102103: add module argument to dataclasses.make_dataclass (#102104)
1 parent ee6f841 commit b48be8f

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

Doc/library/dataclasses.rst

+5-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ Module contents
389389
:func:`astuple` raises :exc:`TypeError` if ``obj`` is not a dataclass
390390
instance.
391391

392-
.. function:: make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False)
392+
.. function:: make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False, match_args=True, kw_only=False, slots=False, weakref_slot=False, module=None)
393393

394394
Creates a new dataclass with name ``cls_name``, fields as defined
395395
in ``fields``, base classes as given in ``bases``, and initialized
@@ -401,6 +401,10 @@ Module contents
401401
``match_args``, ``kw_only``, ``slots``, and ``weakref_slot`` have
402402
the same meaning as they do in :func:`dataclass`.
403403

404+
If ``module`` is defined, the ``__module__`` attribute
405+
of the dataclass is set to that value.
406+
By default, it is set to the module name of the caller.
407+
404408
This function is not strictly required, because any Python
405409
mechanism for creating a new class with ``__annotations__`` can
406410
then apply the :func:`dataclass` function to convert that class to

Lib/dataclasses.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -1391,7 +1391,7 @@ def _astuple_inner(obj, tuple_factory):
13911391
def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
13921392
repr=True, eq=True, order=False, unsafe_hash=False,
13931393
frozen=False, match_args=True, kw_only=False, slots=False,
1394-
weakref_slot=False):
1394+
weakref_slot=False, module=None):
13951395
"""Return a new dynamically created dataclass.
13961396
13971397
The dataclass name will be 'cls_name'. 'fields' is an iterable
@@ -1455,6 +1455,19 @@ def exec_body_callback(ns):
14551455
# of generic dataclasses.
14561456
cls = types.new_class(cls_name, bases, {}, exec_body_callback)
14571457

1458+
# For pickling to work, the __module__ variable needs to be set to the frame
1459+
# where the dataclass is created.
1460+
if module is None:
1461+
try:
1462+
module = sys._getframemodulename(1) or '__main__'
1463+
except AttributeError:
1464+
try:
1465+
module = sys._getframe(1).f_globals.get('__name__', '__main__')
1466+
except (AttributeError, ValueError):
1467+
pass
1468+
if module is not None:
1469+
cls.__module__ = module
1470+
14581471
# Apply the normal decorator.
14591472
return dataclass(cls, init=init, repr=repr, eq=eq, order=order,
14601473
unsafe_hash=unsafe_hash, frozen=frozen,

Lib/test/test_dataclasses.py

+39
Original file line numberDiff line numberDiff line change
@@ -3606,6 +3606,15 @@ def test_text_annotations(self):
36063606
'return': type(None)})
36073607

36083608

3609+
ByMakeDataClass = make_dataclass('ByMakeDataClass', [('x', int)])
3610+
ManualModuleMakeDataClass = make_dataclass('ManualModuleMakeDataClass',
3611+
[('x', int)],
3612+
module='test.test_dataclasses')
3613+
WrongNameMakeDataclass = make_dataclass('Wrong', [('x', int)])
3614+
WrongModuleMakeDataclass = make_dataclass('WrongModuleMakeDataclass',
3615+
[('x', int)],
3616+
module='custom')
3617+
36093618
class TestMakeDataclass(unittest.TestCase):
36103619
def test_simple(self):
36113620
C = make_dataclass('C',
@@ -3715,6 +3724,36 @@ def test_no_types(self):
37153724
'y': int,
37163725
'z': 'typing.Any'})
37173726

3727+
def test_module_attr(self):
3728+
self.assertEqual(ByMakeDataClass.__module__, __name__)
3729+
self.assertEqual(ByMakeDataClass(1).__module__, __name__)
3730+
self.assertEqual(WrongModuleMakeDataclass.__module__, "custom")
3731+
Nested = make_dataclass('Nested', [])
3732+
self.assertEqual(Nested.__module__, __name__)
3733+
self.assertEqual(Nested().__module__, __name__)
3734+
3735+
def test_pickle_support(self):
3736+
for klass in [ByMakeDataClass, ManualModuleMakeDataClass]:
3737+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
3738+
with self.subTest(proto=proto):
3739+
self.assertEqual(
3740+
pickle.loads(pickle.dumps(klass, proto)),
3741+
klass,
3742+
)
3743+
self.assertEqual(
3744+
pickle.loads(pickle.dumps(klass(1), proto)),
3745+
klass(1),
3746+
)
3747+
3748+
def test_cannot_be_pickled(self):
3749+
for klass in [WrongNameMakeDataclass, WrongModuleMakeDataclass]:
3750+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
3751+
with self.subTest(proto=proto):
3752+
with self.assertRaises(pickle.PickleError):
3753+
pickle.dumps(klass, proto)
3754+
with self.assertRaises(pickle.PickleError):
3755+
pickle.dumps(klass(1), proto)
3756+
37183757
def test_invalid_type_specification(self):
37193758
for bad_field in [(),
37203759
(1, 2, 3, 4),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``module`` argument to :func:`dataclasses.make_dataclass` and make
2+
classes produced by it pickleable.

0 commit comments

Comments
 (0)