Hopefully the final set of edits.

This commit is contained in:
Eric V. Smith 2017-11-29 18:21:12 -05:00
parent 2a00e91583
commit 2074efb171
1 changed files with 74 additions and 38 deletions

View File

@ -6,7 +6,7 @@ Type: Standards Track
Content-Type: text/x-rst
Created: 02-Jun-2017
Python-Version: 3.7
Post-History: 08-Sep-2017, 25-Nov-2017
Post-History: 08-Sep-2017, 25-Nov-2017, 30-Nov-2017
Notice for Reviewers
====================
@ -57,7 +57,7 @@ to the InventoryItem class::
self.unit_price = unit_price
self.quantity_on_hand = quantity_on_hand
def __repr__(self):
return f'InventoryItem(name={self.name!r},unit_price={self.unit_price!r},quantity_on_hand={self.quantity_on_hand!r})'
return f'InventoryItem(name={self.name!r}, unit_price={self.unit_price!r}, quantity_on_hand={self.quantity_on_hand!r})'
def __eq__(self, other):
if other.__class__ is self.__class__:
return (self.name, self.unit_price, self.quantity_on_hand) == (other.name, other.unit_price, other.quantity_on_hand)
@ -83,7 +83,7 @@ to the InventoryItem class::
return (self.name, self.unit_price, self.quantity_on_hand) >= (other.name, other.unit_price, other.quantity_on_hand)
return NotImplemented
Data Classes saves you from writing and maintaining these functions.
Data Classes saves you from writing and maintaining these methods.
Rationale
=========
@ -157,11 +157,16 @@ Note that ``__annotations__`` is guaranteed to be an ordered mapping,
in class declaration order. The order of the fields in all of the
generated methods is the order in which they appear in the class.
The ``dataclass`` decorator will add various "dunder" methods to the
class, described below. If any of the added methods already exist on the
class, a ``TypeError`` will be raised. The decorator returns the same
class that is called on: no new class is created.
The ``dataclass`` decorator is typically used with no parameters and
no parentheses. However, it also supports the following logical
signature::
def dataclass(*, init=True, repr=True, eq=True, compare=True, hash=None, frozen=False)
def dataclass(*, init=True, repr=True, eq=True, order=True, hash=None, frozen=False)
If ``dataclass`` is used just as a simple decorator with no
parameters, it acts as if it has the default values documented in this
@ -175,7 +180,7 @@ signature. That is, these three uses of ``@dataclass`` are equivalent::
class C:
...
@dataclass(init=True, repr=True, eq=True, compare=True, hash=None, frozen=False)
@dataclass(init=True, repr=True, eq=True, order=True, hash=None, frozen=False)
class C:
...
@ -184,24 +189,23 @@ The parameters to ``dataclass`` are:
- ``init``: If true (the default), a ``__init__`` method will be
generated.
- ``repr``: If true (the default), a ``__repr__`` function will be
- ``repr``: If true (the default), a ``__repr__`` method will be
generated. The generated repr string will have the class name and
the name and repr of each field, in the order they are defined in
the class. Fields that are marked as being excluded from the repr
are not included. For example:
``InventoryItem(name='widget',unit_price=3.0,quantity_on_hand=10)``.
``InventoryItem(name='widget', unit_price=3.0, quantity_on_hand=10)``.
- ``eq``: If true (the default), ``__eq__`` and ``__ne__`` methods
will be generated. These compare the class as if it were a tuple of
its fields, in order. Both instances in the comparison must be of
the identical type.
- ``compare``: If true (the default), ``__lt__``, ``__le__``,
- ``order``: If true (the default), ``__lt__``, ``__le__``,
``__gt__``, and ``__ge__`` methods will be generated. These compare
the class as if it were a tuple of its fields, in order. Both
instances in the comparison must be of the identical type. If
``compare`` is True, then ``eq`` is ignored, and ``__eq__`` and
``__ne__`` will be automatically generated.
``order`` is true and ``eq`` is false, a ``ValueError`` is raised.
- ``hash``: Either a bool or ``None``. If ``None`` (the default), the
``__hash__`` method is generated according to how ``eq`` and
@ -235,10 +239,14 @@ Python syntax::
b: int = 0 # assign a default value for 'b'
In this example, both ``a`` and ``b`` will be included in the added
``__init__`` function, which will be defined as::
``__init__`` method, which will be defined as::
def __init__(self, a: int, b: int = 0):
``TypeError`` will be raised if a field without a default value
follows a field with a default value. This is true either when this
occurs in a single class, or as a result of class inheritance.
For common and simple use cases, no other functionality is required.
There are, however, some Data Class features that require additional
per-field information. To satisfy this need for additional
@ -266,10 +274,10 @@ The parameters to ``field()`` are:
specify both ``default`` and ``default_factory``.
- ``init``: If true (the default), this field is included as a
parameter to the generated ``__init__`` function.
parameter to the generated ``__init__`` method.
- ``repr``: If true (the default), this field is included in the
string returned by the generated ``__repr__`` function.
string returned by the generated ``__repr__`` method.
- ``compare``: If True (the default), this field is included in the
generated equality and comparison methods (``__eq__``, ``__gt__``,
@ -278,10 +286,16 @@ The parameters to ``field()`` are:
- ``hash``: This can be a bool or ``None``. If True, this field is
included in the generated ``__hash__`` method. If ``None`` (the
default), use the value of ``compare``: this would normally be the
expected behavior. A field needs to be considered in the hash if
expected behavior. A field should be considered in the hash if
it's used for comparisons. Setting this value to anything other
than ``None`` is discouraged.
One possible reason to set ``hash=False`` but ``compare=True`` would
be if a field is expensive to compute a hash value for, that field
is needed for equality testing, and there are other fields that
contribute to the type's hash value. Even if a field is excluded
from the hash, it will still be used for comparisons.
- ``metadata``: This can be a mapping or None. None is treated as an
empty dict. This value is wrapped in ``types.MappingProxyType`` to
make it read-only, and exposed on the Field object. It is not used
@ -291,12 +305,11 @@ The parameters to ``field()`` are:
If the default value of a field is specified by a call to ``field()``,
then the class attribute for this field will be replaced by the
specified ``default`` value, if one is provided in the call to
``field()``. If no ``default`` is provided, then the class attribute
will be deleted. The intent is that after the ``dataclass`` decorator
runs, the class attributes will all contain the default values for the
fields, just as if the default value itself were specified. For
example, after::
specified ``default`` value. If no ``default`` is provided, then the
class attribute will be deleted. The intent is that after the
``dataclass`` decorator runs, the class attributes will all contain
the default values for the fields, just as if the default value itself
were specified. For example, after::
@dataclass
class C:
@ -322,17 +335,19 @@ object directly. Its documented attributes are:
- ``type``: The type of the field.
- ``default``, ``default_factory``, ``init``, ``repr``, ``hash``,
 ``compare``, and ``metadata`` have the identical meaning as they do
 in the ``field()`` declaration.
``compare``, and ``metadata`` have the identical meaning and values
as they do in the ``field()`` declaration.
Other attributes may exist, but they are private.
Other attributes may exist, but they are private and must not be
inspected or relied on.
post-init processing
--------------------
The generated ``__init__`` code will call a method named
``__post_init__``, if it is defined on the class. It will
be called as ``self.__post_init__()``.
``__post_init__``, if it is defined on the class. It will be called
as ``self.__post_init__()``. If not ``__init__`` method is generated,
then ``__post_init__`` will not automatically be called.
Among other uses, this allows for initializing field values that
depend on one or more other fields. For example::
@ -355,12 +370,11 @@ Class variables
One place where ``dataclass`` actually inspects the type of a field is
to determine if a field is a class variable as defined in PEP 526. It
does this by checking if the type of the field is of type
``typing.ClassVar``. If a field is a ``ClassVar``, it is excluded
from consideration as a field and is ignored by the Data Class
mechanisms. For more discussion, see [#]_. Such ``ClassVar``
pseudo-fields are not returned by the module-level ``fields()``
function.
does this by checking if the type of the field is ``typing.ClassVar``.
If a field is a ``ClassVar``, it is excluded from consideration as a
field and is ignored by the Data Class mechanisms. For more
discussion, see [#]_. Such ``ClassVar`` pseudo-fields are not
returned by the module-level ``fields()`` function.
Init-only variables
-------------------
@ -375,7 +389,7 @@ parameters to the generated ``__init__`` method, and are passed to
the optional ``__post_init__`` method. They are not otherwise used
by Data Classes.
For example, suppose a field will be initialized from a database, if a
For example, suppose a field will be initialzed from a database, if a
value is not provided when creating the class::
@dataclass
@ -730,12 +744,16 @@ parameters. They are passed first to ``__init__`` which passes them
to ``__post_init__`` where user code can use them as needed.
The only real difference between alternate classmethod constructors
and ``InitVar`` pseudo-fields is in object creation. With
``InitVar``s, using ``__init__`` and the module-level ``replace()``
function ``InitVar``'s must always be specified. With alternate
classmethod constructors the additional initialization parameters are
always optional. Which approach is more appropriate will be
application-specific, but both approaches are supported.
and ``InitVar`` pseudo-fields is in regards to required non-field
parameters during object creation. With ``InitVar``s, using
``__init__`` and the module-level ``replace()`` function ``InitVar``'s
must always be specified. Consider the case where a ``context``
object is needed to create an instance, but isn't stored as a field.
With alternate classmethod constructors the ``context`` parameter is
always optional, because you could still create the object by going
through ``__init__`` (unless you suppress its creation). Which
approach is more appropriate will be application-specific, but both
approaches are supported.
Rejected ideas
==============
@ -783,6 +801,24 @@ see [#]_.
Examples
========
Custom __init__ method
----------------------
Sometimes the generated ``__init__`` method does not suffice. For
example, suppose you wanted to have an object to store ``*args`` and
``**kwargs``::
@dataclass(init=False)
class ArgHolder:
args: List[Any]
kwargs: Mapping[Any, Any]
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
a = ArgHolder(1, 2, three=3)
A complicated example
---------------------