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