bpo-34776: Fix dataclasses to support future "annotations" mode (… · python/cpython@0d57db2 (original) (raw)
`@@ -379,23 +379,24 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
`
379
379
`# worries about external callers.
`
380
380
`if locals is None:
`
381
381
`locals = {}
`
382
``
`-
builtins may be the "builtins" module or
`
383
``
`-
the value of its "dict",
`
384
``
`-
so make sure "builtins" is the module.
`
385
``
`-
if globals is not None and 'builtins' not in globals:
`
386
``
`-
globals['builtins'] = builtins
`
``
382
`+
if 'BUILTINS' not in locals:
`
``
383
`+
locals['BUILTINS'] = builtins
`
387
384
`return_annotation = ''
`
388
385
`if return_type is not MISSING:
`
389
386
`locals['_return_type'] = return_type
`
390
387
`return_annotation = '->_return_type'
`
391
388
`args = ','.join(args)
`
392
``
`-
body = '\n'.join(f' {b}' for b in body)
`
``
389
`+
body = '\n'.join(f' {b}' for b in body)
`
393
390
``
394
391
`# Compute the text of the entire function.
`
395
``
`-
txt = f'def {name}({args}){return_annotation}:\n{body}'
`
``
392
`+
txt = f' def {name}({args}){return_annotation}:\n{body}'
`
396
393
``
397
``
`-
exec(txt, globals, locals)
`
398
``
`-
return locals[name]
`
``
394
`+
local_vars = ', '.join(locals.keys())
`
``
395
`+
txt = f"def create_fn({local_vars}):\n{txt}\n return {name}"
`
``
396
+
``
397
`+
ns = {}
`
``
398
`+
exec(txt, globals, ns)
`
``
399
`+
return ns'create_fn'
`
399
400
``
400
401
``
401
402
`def _field_assign(frozen, name, value, self_name):
`
`@@ -406,7 +407,7 @@ def _field_assign(frozen, name, value, self_name):
`
406
407
`# self_name is what "self" is called in this function: don't
`
407
408
`# hard-code "self", since that might be a field name.
`
408
409
`if frozen:
`
409
``
`-
return f'builtins.object.setattr({self_name},{name!r},{value})'
`
``
410
`+
return f'BUILTINS.object.setattr({self_name},{name!r},{value})'
`
410
411
`return f'{self_name}.{name}={value}'
`
411
412
``
412
413
``
`@@ -483,7 +484,7 @@ def _init_param(f):
`
483
484
`return f'{f.name}:type{f.name}{default}'
`
484
485
``
485
486
``
486
``
`-
def _init_fn(fields, frozen, has_post_init, self_name):
`
``
487
`+
def _init_fn(fields, frozen, has_post_init, self_name, globals):
`
487
488
`# fields contains both real fields and InitVar pseudo-fields.
`
488
489
``
489
490
`# Make sure we don't have fields without defaults following fields
`
`@@ -501,12 +502,15 @@ def _init_fn(fields, frozen, has_post_init, self_name):
`
501
502
`raise TypeError(f'non-default argument {f.name!r} '
`
502
503
`'follows default argument')
`
503
504
``
504
``
`-
globals = {'MISSING': MISSING,
`
505
``
`-
'_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY}
`
``
505
`+
locals = {f'type{f.name}': f.type for f in fields}
`
``
506
`+
locals.update({
`
``
507
`+
'MISSING': MISSING,
`
``
508
`+
'_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY,
`
``
509
`+
})
`
506
510
``
507
511
`body_lines = []
`
508
512
`for f in fields:
`
509
``
`-
line = _field_init(f, frozen, globals, self_name)
`
``
513
`+
line = _field_init(f, frozen, locals, self_name)
`
510
514
`# line is None means that this field doesn't require
`
511
515
`# initialization (it's a pseudo-field). Just skip it.
`
512
516
`if line:
`
`@@ -522,7 +526,6 @@ def _init_fn(fields, frozen, has_post_init, self_name):
`
522
526
`if not body_lines:
`
523
527
`body_lines = ['pass']
`
524
528
``
525
``
`-
locals = {f'type{f.name}': f.type for f in fields}
`
526
529
`return _create_fn('init',
`
527
530
` [self_name] + [_init_param(f) for f in fields if f.init],
`
528
531
`body_lines,
`
`@@ -531,20 +534,19 @@ def _init_fn(fields, frozen, has_post_init, self_name):
`
531
534
`return_type=None)
`
532
535
``
533
536
``
534
``
`-
def _repr_fn(fields):
`
``
537
`+
def _repr_fn(fields, globals):
`
535
538
`fn = _create_fn('repr',
`
536
539
` ('self',),
`
537
540
` ['return self.class.qualname + f"(' +
`
538
541
`', '.join([f"{f.name}={{self.{f.name}!r}}"
`
539
542
`for f in fields]) +
`
540
``
`-
')"'])
`
``
543
`+
')"'],
`
``
544
`+
globals=globals)
`
541
545
`return _recursive_repr(fn)
`
542
546
``
543
547
``
544
``
`-
def _frozen_get_del_attr(cls, fields):
`
545
``
`-
XXX: globals is modified on the first call to _create_fn, then
`
546
``
`-
the modified version is used in the second call. Is this okay?
`
547
``
`-
globals = {'cls': cls,
`
``
548
`+
def _frozen_get_del_attr(cls, fields, globals):
`
``
549
`+
locals = {'cls': cls,
`
548
550
`'FrozenInstanceError': FrozenInstanceError}
`
549
551
`if fields:
`
550
552
`fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)'
`
`@@ -556,17 +558,19 @@ def _frozen_get_del_attr(cls, fields):
`
556
558
` (f'if type(self) is cls or name in {fields_str}:',
`
557
559
`' raise FrozenInstanceError(f"cannot assign to field {name!r}")',
`
558
560
`f'super(cls, self).setattr(name, value)'),
`
``
561
`+
locals=locals,
`
559
562
`globals=globals),
`
560
563
`_create_fn('delattr',
`
561
564
` ('self', 'name'),
`
562
565
` (f'if type(self) is cls or name in {fields_str}:',
`
563
566
`' raise FrozenInstanceError(f"cannot delete field {name!r}")',
`
564
567
`f'super(cls, self).delattr(name)'),
`
``
568
`+
locals=locals,
`
565
569
`globals=globals),
`
566
570
` )
`
567
571
``
568
572
``
569
``
`-
def _cmp_fn(name, op, self_tuple, other_tuple):
`
``
573
`+
def _cmp_fn(name, op, self_tuple, other_tuple, globals):
`
570
574
`# Create a comparison function. If the fields in the object are
`
571
575
`# named 'x' and 'y', then self_tuple is the string
`
572
576
`# '(self.x,self.y)' and other_tuple is the string
`
`@@ -576,14 +580,16 @@ def _cmp_fn(name, op, self_tuple, other_tuple):
`
576
580
` ('self', 'other'),
`
577
581
` [ 'if other.class is self.class:',
`
578
582
`f' return {self_tuple}{op}{other_tuple}',
`
579
``
`-
'return NotImplemented'])
`
``
583
`+
'return NotImplemented'],
`
``
584
`+
globals=globals)
`
580
585
``
581
586
``
582
``
`-
def _hash_fn(fields):
`
``
587
`+
def _hash_fn(fields, globals):
`
583
588
`self_tuple = _tuple_str('self', fields)
`
584
589
`return _create_fn('hash',
`
585
590
` ('self',),
`
586
``
`-
[f'return hash({self_tuple})'])
`
``
591
`+
[f'return hash({self_tuple})'],
`
``
592
`+
globals=globals)
`
587
593
``
588
594
``
589
595
`def _is_classvar(a_type, typing):
`
`@@ -756,14 +762,14 @@ def _set_new_attribute(cls, name, value):
`
756
762
`# take. The common case is to do nothing, so instead of providing a
`
757
763
`# function that is a no-op, use None to signify that.
`
758
764
``
759
``
`-
def _hash_set_none(cls, fields):
`
``
765
`+
def _hash_set_none(cls, fields, globals):
`
760
766
`return None
`
761
767
``
762
``
`-
def _hash_add(cls, fields):
`
``
768
`+
def _hash_add(cls, fields, globals):
`
763
769
`flds = [f for f in fields if (f.compare if f.hash is None else f.hash)]
`
764
``
`-
return _hash_fn(flds)
`
``
770
`+
return _hash_fn(flds, globals)
`
765
771
``
766
``
`-
def _hash_exception(cls, fields):
`
``
772
`+
def _hash_exception(cls, fields, globals):
`
767
773
`# Raise an exception.
`
768
774
`raise TypeError(f'Cannot overwrite attribute hash '
`
769
775
`f'in class {cls.name}')
`
`@@ -805,6 +811,16 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
805
811
`# is defined by the base class, which is found first.
`
806
812
`fields = {}
`
807
813
``
``
814
`+
if cls.module in sys.modules:
`
``
815
`+
globals = sys.modules[cls.module].dict
`
``
816
`+
else:
`
``
817
`+
Theoretically this can happen if someone writes
`
``
818
`+
a custom string to cls.module. In which case
`
``
819
`+
such dataclass won't be fully introspectable
`
``
820
`+
(w.r.t. typing.get_type_hints) but will still function
`
``
821
`+
correctly.
`
``
822
`+
globals = {}
`
``
823
+
808
824
`setattr(cls, _PARAMS, _DataclassParams(init, repr, eq, order,
`
809
825
`unsafe_hash, frozen))
`
810
826
``
`@@ -914,6 +930,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
914
930
`# if possible.
`
915
931
`'dataclass_self' if 'self' in fields
`
916
932
`else 'self',
`
``
933
`+
globals,
`
917
934
` ))
`
918
935
``
919
936
`# Get the fields as a list, and include only real fields. This is
`
`@@ -922,7 +939,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
922
939
``
923
940
`if repr:
`
924
941
`flds = [f for f in field_list if f.repr]
`
925
``
`-
_set_new_attribute(cls, 'repr', _repr_fn(flds))
`
``
942
`+
_set_new_attribute(cls, 'repr', _repr_fn(flds, globals))
`
926
943
``
927
944
`if eq:
`
928
945
`# Create eq_ method. There's no need for a ne method,
`
`@@ -932,7 +949,8 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
932
949
`other_tuple = _tuple_str('other', flds)
`
933
950
`_set_new_attribute(cls, 'eq',
`
934
951
`_cmp_fn('eq', '==',
`
935
``
`-
self_tuple, other_tuple))
`
``
952
`+
self_tuple, other_tuple,
`
``
953
`+
globals=globals))
`
936
954
``
937
955
`if order:
`
938
956
`# Create and set the ordering methods.
`
`@@ -945,13 +963,14 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
945
963
` ('ge', '>='),
`
946
964
` ]:
`
947
965
`if _set_new_attribute(cls, name,
`
948
``
`-
_cmp_fn(name, op, self_tuple, other_tuple)):
`
``
966
`+
_cmp_fn(name, op, self_tuple, other_tuple,
`
``
967
`+
globals=globals)):
`
949
968
`raise TypeError(f'Cannot overwrite attribute {name} '
`
950
969
`f'in class {cls.name}. Consider using '
`
951
970
`'functools.total_ordering')
`
952
971
``
953
972
`if frozen:
`
954
``
`-
for fn in _frozen_get_del_attr(cls, field_list):
`
``
973
`+
for fn in _frozen_get_del_attr(cls, field_list, globals):
`
955
974
`if _set_new_attribute(cls, fn.name, fn):
`
956
975
`raise TypeError(f'Cannot overwrite attribute {fn.name} '
`
957
976
`f'in class {cls.name}')
`
`@@ -964,7 +983,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
`
964
983
`if hash_action:
`
965
984
`# No need to call _set_new_attribute here, since by the time
`
966
985
`# we're here the overwriting is unconditional.
`
967
``
`-
cls.hash = hash_action(cls, field_list)
`
``
986
`+
cls.hash = hash_action(cls, field_list, globals)
`
968
987
``
969
988
`if not getattr(cls, 'doc'):
`
970
989
`# Create a class doc-string.
`