This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eric.smith
Recipients eric.smith, levkivskyi
Date 2018-01-22.02:41:19
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1516588881.05.0.467229070634.issue32513@psf.upfronthosting.co.za>
In-reply-to
Content
Here is my proposal for making it easier for the user to supply dunder methods that dataclasses would otherwise automatically add to the class.

For all of these cases, when I talk about init=, repr=, eq=, order=, hash=, or frozen=, I'm referring to the parameters to the dataclass decorator.

When checking if a dunder method already exists, I mean check for an entry in the class's __dict__. I never check to see if a member is defined in a base class.

Let's get the easy ones out of the way first.

__init__
If __init__ exists or init=False, don't generate __init__.

__repr__
If __repr__ exists or repr=False, don't generate __repr__.

__setattr__
__delattr__
If frozen=True and either of these methods exist, raise a TypeError. These methods are needed for the "frozen-ness" of the class to be implemented, and if they're already set then that behavior can't be enforced.

__eq__
If __eq__ exists or eq=False, don't generate __eq__.

And now the harder ones:

__ne__
I propose to never generate a __ne__ method. Python will call __eq__ and negate the result for you. If you feel you really need a non-matching __ne__, then you can write it yourself without interference from dataclasses.

__lt__
__le__
__gt__
__ge__
I propose to treat each of these individually, but for each of them using the value of the order= parameter. So:
If __lt__ exists or order=False, don't generate __lt__.
If __le__ exists or order=False, don't generate __le__.
If __gt__ exists or order=False, don't generate __gt__.
If __ge__ exists or order=False, don't generate __ge__.
If for some crazy reason you want to define some of these but not others, then set order=False and write your desired methods.

__hash__
Whether dataclasses might generate __hash__ depends on the values of hash=, eq=, and frozen=. Note that the default value of hash= is None. See below for an explanation.

If hash=False, never generate __hash__. If hash=True, generate __hash__ unless it already exists.

If hash=None (the default), then use this table to decide whether and how to generate __hash__:
eq=?	frozen=?	__hash__
False	False		do not generate __hash__
False	True		do not generate __hash__
True	False		set __hash__ to None unless it already exists
True	True		generate __hash__ unless it already exists
                         and is None

Note that it's almost always a bad idea to specify hash=True or hash=False. The action based on the above table (where hash=None, the default), is usually the correct behavior.

One special case to recognize is if the class defines a __eq__. In this case, Python will assign __hash__=None before the dataclass decorator is called. The decorator cannot distinguish between these two cases (except possibly by using the order of __dict__ keys, but that seems overly fragile):

@dataclass
class A:
    def __eq__(self, other): pass

@dataclass
class B:
    def __eq__(self, other): pass
    __hash__ = None

This is the source of the last line in the above table: for a dataclass where eq=True, frozen=True, and hash=None, if __hash__ is None it will still be overwritten. The assumption is that this is what the user wants, but it's a tricky corner case. It also occurs if setting hash=True and defining __eq__. Again, it's not expected to come up in normal usage.
History
Date User Action Args
2018-01-22 02:41:21eric.smithsetrecipients: + eric.smith, levkivskyi
2018-01-22 02:41:21eric.smithsetmessageid: <1516588881.05.0.467229070634.issue32513@psf.upfronthosting.co.za>
2018-01-22 02:41:21eric.smithlinkissue32513 messages
2018-01-22 02:41:19eric.smithcreate