ericvsmith / dataclasses

Apache License 2.0
587 stars 53 forks source link

Changing Field Order In Subclass #129

Closed dsanders11 closed 6 years ago

dsanders11 commented 6 years ago

Really enjoying the dataclass decorator and can't wait for it to be standard in Python 3.7. I've been playing around with it in 3.6 and came across a use-case that I can't seem to make work intuitively. Basically, Point2D is a point with a units field, and a default value to simplify things. Blob is a subclass of Point2D which is simply a point with a diameter. It needs to be a subclass so that it can inherit the helper methods on Point2D and so type-checking allows it to be used anywhere Point2D can be.

from dataclasses import dataclass
from enum import Enum

class Unit(Enum):
    Millimeters = 'mm'
    Inches = 'in'

@dataclass
class Point2D:
    x: float
    y: float
    units: Unit = Unit.Inches

    def distance_to(self, point: 'Point2D') -> float:
        """ Calculate distance to another point """

@dataclass
class Blob(Point2D):
    diameter: float

This seems to run into two main issues. First, Blob can't add a required diameter field as it would cause a non-default argument to follow a default argument in the generated __init__. This could be worked around with a custom __init__, but that would still leave us with the second problem: the order of the fields in repr and astuple. Ideally we'd want those to be: x, y, diameter, units as that makes more sense than x, y, units, diameter.

What if class inheritance where the child class defines a superset of the parent's fields was a special case which re-defined the order of the fields? Would turn the above Blob definition into:

@dataclass
class Blob(Point2D):
    x: float
    y: float
    diameter: float
    units: Unit = Unit.Inches

I suppose this is a bit 'dangerous' as it's magic and someone could inadvertently redefine the order of the fields while subclassing when they simply intended to give all fields a default, but listed them in a slightly different order. As such, perhaps it could be enabled by a new keyword arg to dataclass, such as allow_reorder.

Implementation could be pretty straightforward, before line 802 add something like:

if set([f.name for f in cls_fields]).issuperset(set(fields.keys())):
    fields.clear()

Actually, while typing this all up I realized that the implementation was simple enough that I just went ahead and did it in my own fork, 183ccfea97feab12a657bd1971db816a7ae0998d. Only played with it a little but so far so good, it's not a very complicated change.

ericvsmith commented 6 years ago

Can you bring this up on the python-ideas mailing list? This repo is just for the backport to Python 3.6. Thanks.

toejough commented 6 years ago

For anyone else hoping for this feature and coming here for some info - this seems to have died after hitting python-ideas. The last mention of it I can find says maybe this could be a thing in 3.8: https://mail.python.org/pipermail/python-ideas/2018-January/048817.html

Not really familiar with the PEP creation process, but I don't see any that look related to this yet: https://www.python.org/dev/peps/#open-peps-under-consideration.