Unverified Kaydet (Commit) f96ddade authored tarafından Eric V. Smith's avatar Eric V. Smith Kaydeden (comit) GitHub

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.
üst a95d9860
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', ...@@ -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', ...@@ -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: ...@@ -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: ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment