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.

`