from typing import Optional
@dataclass
class Event:
id: str
created_at: datetime
updated_at: Optional[datetime]
#updated_at: datetime = field(default_factory=datetime.now) CASE 1
#updated_at: Optional[datetime] = None CASE 2
@dataclass
class NamedEvent(Event):
name: str
When creating an event instance I will generally not have an updated_at
field. I can either pass the current time
as default value or add a value to it when making the insertion in the database and fetch it in subsequent uses of the object. Which is the better way?
As per my understanding, I cannot create a NamedEvent
instance without passing the updated_at field in case1 and case2 since I do not have a default value in name field.
In this post, we will discuss how DataClasses behave when inherited. Though they make their own constructors, DataClasses behave pretty much the same way as normal classes do when inherited.
dataclasses has a special method called __post_init__ . As the name clearly suggests, this method is called right after the __init__ method is called. Going back to the previous example, we can see how this method can be called to initialize an internal attribute that depends on previously set attributes.
The __post_init__ method is called just after initialization. In other words, it is called after the object receives values for its fields, such as name , continent , population , and official_lang .
dataclass module is introduced in Python 3.7 as a utility tool to make structured classes specially for storing data. These classes hold certain properties and functions to deal specifically with the data and its representation.
The underlying problem that you have seems to be the same one that is described here. The short version of that post is that in a function signature (including the dataclass-generated __init__
method), obligatory arguments (like NamedEvent's name
) can not follow after arguments with default values (which are necessary to define the behavior of Event's updated_at
) - a child's fields will always follow after those of its parent.
So either you have no default values in your parent class (which doesn't work in this case) or all your child's fields need default values (which is annoying, and sometimes simply not feasible).
The post I linked above discusses some patterns that you can apply to solve your problem, but as a nicer alternative you can also use the third part party package pydantic
which already solved this problem for you. A sample implementation could look like this:
import pydantic
from datetime import datetime
class Event(pydantic.BaseModel):
id: str
created_at: datetime = None
updated_at: datetime = None
@pydantic.validator('created_at', pre=True, always=True)
def default_created(cls, v):
return v or datetime.now()
@pydantic.validator('updated_at', pre=True, always=True)
def default_modified(cls, v, values):
return v or values['created_at']
class NamedEvent(Event):
name: str
The default-value specification through validators is a bit cumbersome, but overall it's a very useful package that fixes lots of the shortcomings that you run into when using dataclasses, plus some more.
Using the class definition, an instance of NamedEvent
can be created like this:
>>> NamedEvent(id='1', name='foo')
NamedEvent(id='1', created_at=datetime.datetime(2020, 5, 2, 18, 50, 12, 902732), updated_at=datetime.datetime(2020, 5, 2, 18, 50, 12, 902732), name='foo')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With