Trivial dataclass cleanups: (GH-6218)
- When adding a single element to a list, use .append() instead of += and creating a new list. - For consistency, import the copy module, instead of just deepcopy. This leaves only a module at the class level, instead of a function. - Improve some comments. - Improve some whitespace. - Use tuples instead of lists. - Simplify a test.
This commit is contained in:
parent
a95d98607e
commit
f96ddade00
@ -1,6 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
import copy
|
||||||
import types
|
import types
|
||||||
from copy import deepcopy
|
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
__all__ = ['dataclass',
|
__all__ = ['dataclass',
|
||||||
@ -137,7 +137,7 @@ __all__ = ['dataclass',
|
|||||||
# For boxes that are blank, __hash__ is untouched and therefore
|
# For boxes that are blank, __hash__ is untouched and therefore
|
||||||
# inherited from the base class. If the base is object, then
|
# inherited from the base class. If the base is object, then
|
||||||
# id-based hashing is used.
|
# id-based hashing is used.
|
||||||
# Note that a class may have already __hash__=None if it specified an
|
# Note that a class may already have __hash__=None if it specified an
|
||||||
# __eq__ method in the class body (not one that was created by
|
# __eq__ method in the class body (not one that was created by
|
||||||
# @dataclass).
|
# @dataclass).
|
||||||
# See _hash_action (below) for a coded version of this table.
|
# See _hash_action (below) for a coded version of this table.
|
||||||
@ -147,7 +147,7 @@ __all__ = ['dataclass',
|
|||||||
class FrozenInstanceError(AttributeError): pass
|
class FrozenInstanceError(AttributeError): pass
|
||||||
|
|
||||||
# A sentinel object for default values to signal that a
|
# A sentinel object for default values to signal that a
|
||||||
# default-factory will be used.
|
# default factory will be used.
|
||||||
# This is given a nice repr() which will appear in the function
|
# This is given a nice repr() which will appear in the function
|
||||||
# signature of dataclasses' constructors.
|
# signature of dataclasses' constructors.
|
||||||
class _HAS_DEFAULT_FACTORY_CLASS:
|
class _HAS_DEFAULT_FACTORY_CLASS:
|
||||||
@ -249,6 +249,7 @@ class _DataclassParams:
|
|||||||
'unsafe_hash',
|
'unsafe_hash',
|
||||||
'frozen',
|
'frozen',
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, init, repr, eq, order, unsafe_hash, frozen):
|
def __init__(self, init, repr, eq, order, unsafe_hash, frozen):
|
||||||
self.init = init
|
self.init = init
|
||||||
self.repr = repr
|
self.repr = repr
|
||||||
@ -267,6 +268,7 @@ class _DataclassParams:
|
|||||||
f'frozen={self.frozen}'
|
f'frozen={self.frozen}'
|
||||||
')')
|
')')
|
||||||
|
|
||||||
|
|
||||||
# This function is used instead of exposing Field creation directly,
|
# This function is used instead of exposing Field creation directly,
|
||||||
# so that a type checker can be told (via overloads) that this is a
|
# so that a type checker can be told (via overloads) that this is a
|
||||||
# function whose type depends on its parameters.
|
# function whose type depends on its parameters.
|
||||||
@ -307,6 +309,8 @@ def _tuple_str(obj_name, fields):
|
|||||||
def _create_fn(name, args, body, *, globals=None, locals=None,
|
def _create_fn(name, args, body, *, globals=None, locals=None,
|
||||||
return_type=MISSING):
|
return_type=MISSING):
|
||||||
# Note that we mutate locals when exec() is called. Caller beware!
|
# Note that we mutate locals when exec() is called. Caller beware!
|
||||||
|
# The only callers are internal to this module, so no worries
|
||||||
|
# about external callers.
|
||||||
if locals is None:
|
if locals is None:
|
||||||
locals = {}
|
locals = {}
|
||||||
return_annotation = ''
|
return_annotation = ''
|
||||||
@ -429,18 +433,17 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
|||||||
|
|
||||||
body_lines = []
|
body_lines = []
|
||||||
for f in fields:
|
for f in fields:
|
||||||
# Do not initialize the pseudo-fields, only the real ones.
|
|
||||||
line = _field_init(f, frozen, globals, self_name)
|
line = _field_init(f, frozen, globals, self_name)
|
||||||
if line is not None:
|
|
||||||
# line is None means that this field doesn't require
|
# line is None means that this field doesn't require
|
||||||
# initialization. Just skip it.
|
# initialization (it's a pseudo-field). Just skip it.
|
||||||
|
if line:
|
||||||
body_lines.append(line)
|
body_lines.append(line)
|
||||||
|
|
||||||
# Does this class have a post-init function?
|
# Does this class have a post-init function?
|
||||||
if has_post_init:
|
if has_post_init:
|
||||||
params_str = ','.join(f.name for f in fields
|
params_str = ','.join(f.name for f in fields
|
||||||
if f._field_type is _FIELD_INITVAR)
|
if f._field_type is _FIELD_INITVAR)
|
||||||
body_lines += [f'{self_name}.{_POST_INIT_NAME}({params_str})']
|
body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})')
|
||||||
|
|
||||||
# If no body lines, use 'pass'.
|
# If no body lines, use 'pass'.
|
||||||
if not body_lines:
|
if not body_lines:
|
||||||
@ -448,7 +451,7 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
|||||||
|
|
||||||
locals = {f'_type_{f.name}': f.type for f in fields}
|
locals = {f'_type_{f.name}': f.type for f in fields}
|
||||||
return _create_fn('__init__',
|
return _create_fn('__init__',
|
||||||
[self_name] +[_init_param(f) for f in fields if f.init],
|
[self_name] + [_init_param(f) for f in fields if f.init],
|
||||||
body_lines,
|
body_lines,
|
||||||
locals=locals,
|
locals=locals,
|
||||||
globals=globals,
|
globals=globals,
|
||||||
@ -457,7 +460,7 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
|||||||
|
|
||||||
def _repr_fn(fields):
|
def _repr_fn(fields):
|
||||||
return _create_fn('__repr__',
|
return _create_fn('__repr__',
|
||||||
['self'],
|
('self',),
|
||||||
['return self.__class__.__qualname__ + f"(' +
|
['return self.__class__.__qualname__ + f"(' +
|
||||||
', '.join([f"{f.name}={{self.{f.name}!r}}"
|
', '.join([f"{f.name}={{self.{f.name}!r}}"
|
||||||
for f in fields]) +
|
for f in fields]) +
|
||||||
@ -496,7 +499,7 @@ def _cmp_fn(name, op, self_tuple, other_tuple):
|
|||||||
# '(other.x,other.y)'.
|
# '(other.x,other.y)'.
|
||||||
|
|
||||||
return _create_fn(name,
|
return _create_fn(name,
|
||||||
['self', 'other'],
|
('self', 'other'),
|
||||||
[ 'if other.__class__ is self.__class__:',
|
[ 'if other.__class__ is self.__class__:',
|
||||||
f' return {self_tuple}{op}{other_tuple}',
|
f' return {self_tuple}{op}{other_tuple}',
|
||||||
'return NotImplemented'])
|
'return NotImplemented'])
|
||||||
@ -505,12 +508,12 @@ def _cmp_fn(name, op, self_tuple, other_tuple):
|
|||||||
def _hash_fn(fields):
|
def _hash_fn(fields):
|
||||||
self_tuple = _tuple_str('self', fields)
|
self_tuple = _tuple_str('self', fields)
|
||||||
return _create_fn('__hash__',
|
return _create_fn('__hash__',
|
||||||
['self'],
|
('self',),
|
||||||
[f'return hash({self_tuple})'])
|
[f'return hash({self_tuple})'])
|
||||||
|
|
||||||
|
|
||||||
def _get_field(cls, a_name, a_type):
|
def _get_field(cls, a_name, a_type):
|
||||||
# Return a Field object, for this field name and type. ClassVars
|
# Return a Field object for this field name and type. ClassVars
|
||||||
# and InitVars are also returned, but marked as such (see
|
# and InitVars are also returned, but marked as such (see
|
||||||
# f._field_type).
|
# f._field_type).
|
||||||
|
|
||||||
@ -560,8 +563,8 @@ def _get_field(cls, a_name, a_type):
|
|||||||
raise TypeError(f'field {f.name} cannot have a '
|
raise TypeError(f'field {f.name} cannot have a '
|
||||||
'default factory')
|
'default factory')
|
||||||
# Should I check for other field settings? default_factory
|
# Should I check for other field settings? default_factory
|
||||||
# seems the most serious to check for. Maybe add others. For
|
# seems the most serious to check for. Maybe add others.
|
||||||
# example, how about init=False (or really,
|
# For example, how about init=False (or really,
|
||||||
# init=<not-the-default-init-value>)? It makes no sense for
|
# init=<not-the-default-init-value>)? It makes no sense for
|
||||||
# ClassVar and InitVar to specify init=<anything>.
|
# ClassVar and InitVar to specify init=<anything>.
|
||||||
|
|
||||||
@ -903,7 +906,7 @@ def _asdict_inner(obj, dict_factory):
|
|||||||
return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
|
return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory))
|
||||||
for k, v in obj.items())
|
for k, v in obj.items())
|
||||||
else:
|
else:
|
||||||
return deepcopy(obj)
|
return copy.deepcopy(obj)
|
||||||
|
|
||||||
|
|
||||||
def astuple(obj, *, tuple_factory=tuple):
|
def astuple(obj, *, tuple_factory=tuple):
|
||||||
@ -943,7 +946,7 @@ def _astuple_inner(obj, tuple_factory):
|
|||||||
return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory))
|
return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory))
|
||||||
for k, v in obj.items())
|
for k, v in obj.items())
|
||||||
else:
|
else:
|
||||||
return deepcopy(obj)
|
return copy.deepcopy(obj)
|
||||||
|
|
||||||
|
|
||||||
def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
|
def make_dataclass(cls_name, fields, *, bases=(), namespace=None, init=True,
|
||||||
@ -1032,9 +1035,9 @@ def replace(obj, **changes):
|
|||||||
if f.name not in changes:
|
if f.name not in changes:
|
||||||
changes[f.name] = getattr(obj, f.name)
|
changes[f.name] = getattr(obj, f.name)
|
||||||
|
|
||||||
# Create the new object, which calls __init__() and __post_init__
|
# Create the new object, which calls __init__() and
|
||||||
# (if defined), using all of the init fields we've added and/or
|
# __post_init__() (if defined), using all of the init fields
|
||||||
# left in 'changes'.
|
# we've added and/or left in 'changes'. If there are values
|
||||||
# If there are values supplied in changes that aren't fields, this
|
# supplied in changes that aren't fields, this will correctly
|
||||||
# will correctly raise a TypeError.
|
# raise a TypeError.
|
||||||
return obj.__class__(**changes)
|
return obj.__class__(**changes)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user