msg395427 - (view) |
Author: Micael Jarniac (MicaelJarniac) * |
Date: 2021-06-09 15:33 |
https://docs.python.org/3/library/dataclasses.html#post-init-processing
https://github.com/python/cpython/blob/3.9/Doc/library/dataclasses.rst#post-init-processing
In the example, a base class "Rectangle" is defined, and then a "Square" class inherits from it.
On reading the example, it seems like the Square class is meant to be used like:
>>> square = Square(5)
Since the Square class seems to be supposed to be a "shortcut" to creating a Rectangle with equal sides.
However, the Rectangle class has two required init arguments, and when Square inherits from it, those arguments are still required, so using Square like in the above example, with a single argument, results in an error:
>>> square = Square(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() missing 2 required positional arguments: 'width' and 'side'
To "properly" use the Square class, it'd need to be instantiated like so:
>>> square = Square(0, 0, 5)
>>> square
Square(height=5, width=5, side=5)
Which, in my opinion, is completely counter-intuitive, and basically invalidates this example.
|
msg395449 - (view) |
Author: Eric V. Smith (eric.smith) * |
Date: 2021-06-09 17:42 |
Agreed that that's not a good (or even workable) example. Thanks for pointing it out.
I'll come up with something better.
|
msg395537 - (view) |
Author: Eric V. Smith (eric.smith) * |
Date: 2021-06-10 13:31 |
The example was added in https://github.com/python/cpython/pull/25967
When reviewing it, I think I missed the fact that the base class is a dataclass. The example and text make more sense if Rectangle isn't a dataclass. Still, I don't like the example at all. I think deleting it might be the best thing to do. Or maybe come up with a case where the base class is some existing class in the stdlib that isn't a dataclass.
|
msg395541 - (view) |
Author: Micael Jarniac (MicaelJarniac) * |
Date: 2021-06-10 15:11 |
I'm trying to think of an example, and what I've thought of so far is having a base dataclass that has a `__post_init__` method, and another dataclass that inherits from it and also has a `__post_init__` method.
In that case, the subclass might need to call `super().__post_init__()` inside its own `__post_init__` method, because otherwise, that wouldn't get called automatically.
Something along those lines:
>>> from dataclasses import dataclass, field
>>>
>>> @dataclass
... class A:
... x: int
... y: int
... xy: int = field(init=False)
...
... def __post_init__(self) -> None:
... self.xy = self.x * self.y
...
>>> @dataclass
... class B(A):
... m: int
... n: int
... mn: int = field(init=False)
...
... def __post_init__(self) -> None:
... super().__post_init__()
... self.mn = self.m * self.n
...
>>> b = B(x=2, y=4, m=3, n=6)
>>> b
B(x=2, y=4, xy=8, m=3, n=6, mn=18)
In this example, if not for the `super().__post_init__()` call inside B's `__post_init__`, we'd get an error `AttributeError: 'B' object has no attribute 'xy'`.
I believe this could be an actual pattern that could be used when dealing with dataclasses.
|
msg395573 - (view) |
Author: Eric V. Smith (eric.smith) * |
Date: 2021-06-10 20:54 |
I'm not sure directly calling __post_init__ is a good pattern. Why would not calling __init__, like you would with any other class, not be the preferred thing to do?
|
msg395596 - (view) |
Author: Micael Jarniac (MicaelJarniac) * |
Date: 2021-06-11 02:17 |
Well, at least for this example, to call `super().__init__()`, I'd need to provide it the two arguments it expects, `x` and `y`, otherwise it'd give an error:
> TypeError: __init__() missing 2 required positional arguments: 'x' and 'y'
If I try calling it as `super().__init__(self.x, self.y)`, I get an infinite recursion error:
> RecursionError: maximum recursion depth exceeded while calling a Python object
That's mostly why I've chosen to call `__post_init__` instead.
And if we're dealing with `InitVar`s, they can nicely be chained like so:
>>> from dataclasses import dataclass, field, InitVar
>>>
>>> @dataclass
... class A:
... x: int
... y: InitVar[int]
... xy: int = field(init=False)
...
... def __post_init__(self, y: int) -> None:
... self.xy = self.x * y
...
>>> @dataclass
... class B(A):
... m: int
... n: InitVar[int]
... mn: int = field(init=False)
...
... def __post_init__(self, y: int, n: int) -> None:
... super().__post_init__(y)
... self.mn = self.m * n
...
>>> b = B(x=2, y=4, m=3, n=6)
>>> b
B(x=2, xy=8, m=3, mn=18)
|
msg395861 - (view) |
Author: Andrei Kulakov (andrei.avk) * |
Date: 2021-06-15 05:07 |
How about this example:
@dataclass
class Rect:
x: int
y: int
r=Rect(5,2)
@dataclass
class HyperRect(Rect):
z: int
def __post_init__(self):
self.vol = self.x*self.y*self.z
hr=HyperRect(5,2,3)
print("hr.vol", hr.vol)
Hyper Rectangle:
https://en.wikipedia.org/wiki/Hyperrectangle
|
msg395999 - (view) |
Author: Eric V. Smith (eric.smith) * |
Date: 2021-06-17 12:22 |
I was thinking about something like:
@dataclass
class FtpHelper(ftplib.FTP):
my_host: str
my_user: str
lookup_password: InitVar[Callable]
def __post_init__(self, lookup_password):
super().__init__(host=self.my_host, user=self.my_user, passwd=lookup_password())
def get_password():
return "a password"
ftp = FtpHelper(hostname, username, get_password)
|
msg396029 - (view) |
Author: Andrei Kulakov (andrei.avk) * |
Date: 2021-06-18 03:32 |
It's a good example, but some readers might only have a vague idea (if any) of what FTP is, so a self contained example might be easier to digest?
|
msg405406 - (view) |
Author: (da2ce7) |
Date: 2021-10-31 16:08 |
I have made a slightly more comprehensive example. See file attached.
Please consider for the updated documentation.
|
msg405408 - (view) |
Author: (da2ce7) |
Date: 2021-10-31 17:10 |
Upon Self Review, I think that this slightly updated version is a bit more illustrative.
|
msg405410 - (view) |
Author: (da2ce7) |
Date: 2021-10-31 17:30 |
Amazingly, the original example needs a very small change to make it work as expected:
@dataclass
class Rectangle:
height: float
width: float
@dataclass
class Square(Rectangle):
side: float
height: float = field(init=False)
width: float = field(init=False)
def __post_init__(self) -> None:
super().__init__(self.side, self.side)
I discover this now, after playing around for a while.
Attached is the simplified version of my expanded example testcase.
|
|
Date |
User |
Action |
Args |
2022-04-11 14:59:46 | admin | set | github: 88531 |
2021-10-31 17:30:04 | da2ce7 | set | files:
+ dataclass_inheritance_v3_test.py
messages:
+ msg405410 |
2021-10-31 17:10:54 | da2ce7 | set | files:
+ dataclass_inheritance_v2_test.py
messages:
+ msg405408 |
2021-10-31 16:08:57 | da2ce7 | set | files:
+ dataclass_inheritance_test.py nosy:
+ da2ce7 messages:
+ msg405406
|
2021-06-18 03:32:46 | andrei.avk | set | messages:
+ msg396029 |
2021-06-17 12:22:06 | eric.smith | set | messages:
+ msg395999 |
2021-06-15 05:07:56 | andrei.avk | set | nosy:
+ andrei.avk messages:
+ msg395861
|
2021-06-11 02:17:40 | MicaelJarniac | set | messages:
+ msg395596 |
2021-06-10 20:54:18 | eric.smith | set | messages:
+ msg395573 |
2021-06-10 15:11:05 | MicaelJarniac | set | messages:
+ msg395541 |
2021-06-10 13:31:36 | eric.smith | set | messages:
+ msg395537 |
2021-06-09 17:42:49 | eric.smith | set | assignee: docs@python -> eric.smith messages:
+ msg395449 versions:
+ Python 3.9, Python 3.10, Python 3.11 |
2021-06-09 15:44:02 | xtreak | set | nosy:
+ eric.smith
|
2021-06-09 15:33:08 | MicaelJarniac | create | |