It's not possible to use a dataclass to make an attribute that sometimes exists and sometimes doesn't because the generated __init__, __eq__, __repr__, etc hard-code which attributes they check.
However, it is possible to make a dataclass with an optional argument that uses a default value for an attribute (when it's not provided).
from dataclasses import dataclass
from typing import Optional
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: Optional[int] = None
kennys_stuff = {
'knife':True,
'fork': True
}
print(CampingEquipment(**kennys_stuff))
And it's possible to make a dataclass with an argument that's accepted to __init__ but isn't an actual field.
So you could do something like this:
from dataclasses import dataclass, InitVar
from typing import Optional
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: InitVar[Optional[int]] = None
def __post_init__(self, missing_flask_size):
if missing_flask_size is not None:
self.missing_flask_size = missing_flask_size
If you really want classes to either to have that attribute present or not have it at all, you could subclass your dataclass and make a factory function that creates one class or the other based on whether that missing_flask_size attribute is present:
from dataclasses import dataclass
@dataclass
class CampingEquipment:
knife: bool
fork: bool
@dataclass
class CampingEquipmentWithFlask(CampingEquipment):
missing_flask_size: int
def equipment(**fields):
if 'missing_flask_size' in fields:
return CampingEquipmentWithFlask(**fields)
return CampingEquipment(**fields)
kennys_stuff = {
'knife':True,
'fork': True
}
print(equipment(**kennys_stuff))
If you really wanted to (I wouldn't recommend it though), you could even customize the __new__ of CampingEquipment to return an instance of that special subclass when that missing_flask_size argument is given (though then you'd need to set init=False and make your own __init__ as well on that class).
It's not possible to use a dataclass to make an attribute that sometimes exists and sometimes doesn't because the generated __init__, __eq__, __repr__, etc hard-code which attributes they check.
However, it is possible to make a dataclass with an optional argument that uses a default value for an attribute (when it's not provided).
from dataclasses import dataclass
from typing import Optional
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: Optional[int] = None
kennys_stuff = {
'knife':True,
'fork': True
}
print(CampingEquipment(**kennys_stuff))
And it's possible to make a dataclass with an argument that's accepted to __init__ but isn't an actual field.
So you could do something like this:
from dataclasses import dataclass, InitVar
from typing import Optional
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: InitVar[Optional[int]] = None
def __post_init__(self, missing_flask_size):
if missing_flask_size is not None:
self.missing_flask_size = missing_flask_size
If you really want classes to either to have that attribute present or not have it at all, you could subclass your dataclass and make a factory function that creates one class or the other based on whether that missing_flask_size attribute is present:
from dataclasses import dataclass
@dataclass
class CampingEquipment:
knife: bool
fork: bool
@dataclass
class CampingEquipmentWithFlask(CampingEquipment):
missing_flask_size: int
def equipment(**fields):
if 'missing_flask_size' in fields:
return CampingEquipmentWithFlask(**fields)
return CampingEquipment(**fields)
kennys_stuff = {
'knife':True,
'fork': True
}
print(equipment(**kennys_stuff))
If you really wanted to (I wouldn't recommend it though), you could even customize the __new__ of CampingEquipment to return an instance of that special subclass when that missing_flask_size argument is given (though then you'd need to set init=False and make your own __init__ as well on that class).
A field object is supposed to be used with =, like a default value, not : like an annotation.
Specifying init=False for a field means the caller can't pass in a value for it at all. init=False fields are supposed to be set in __post_init__, like this:
@dataclass
class Example:
a: int
b: int
c: int = field(init=False)
def __post_init__(self):
self.c = self.a + self.b
print(Example(1, 2).c) # prints 3
If you want to make it optional for the caller to provide a value, you can set a default value. If the caller doesn't provide a value, the default will be used instead:
@dataclass
class Example:
a: int
b: int
c: int = -1
print(Example(1, 2).c) # prints -1
print(Example(1, 2, 3).c) # prints 3
"Arguments missing" for optional arguments in dataclass
python - How to create an optional field in a dataclass that is inherited? - Stack Overflow
Mypy does not infer that a dataclass field is optional when None is assigned
python - Can I have an optional parameter in dataclasses that is omitted when transformed to dict? - Stack Overflow
Videos
I am testing the contents of some messages, and I have a bit of a conundrum. I have a spec for the messages, which includes a number of required and optional fields (the spec is AsyncAPI 2.5.0, if that helps). The spec gives a number of fields that are explicitly required, but are allowed to have a value of null.
I'm using marshmallow-dataclass to build the schema:
from typing import ClassVar, Type
from marshmallow import Schema
from marshmallow_dataclass import dataclass
@dataclass
class RequiredButNullable:
Schema: ClassVar[Type[Schema]]
required_field: str # Required, cannot be null
required_but_nullable: int # this field must be present, but can be null
some_other_field: float | None # optional field, can be missingIf I load a valid message with required_but_nullable set to null, it throws a validation error, but if I change the typing to int | None, it allows an invalid message that is missing required_but_nullable.
Is there a way to define a custom type, e.g. Nullable[int], or do I need to add some form of validation function?
You can have an optional parameter in data class by setting a default value, for example an empty string "" or None.
from dataclasses import dataclass
from typings import Optional
@dataclass
class SubOrder:
n: Optional[int] = None
name: str = ""
from dataclasses import asdict, dataclass
from typing import List, Optional
from validated_dc import ValidatedDC
@dataclass
class SubOrder(ValidatedDC):
name: str
@dataclass
class Order(ValidatedDC):
name: str
sub_orders: Optional[List[SubOrder]] = None
def as_dict(self):
data = asdict(self)
return {key: value for key, value in data.items() if value is not None}
data = {'name': 'pizza'}
order = Order(**data)
assert order.get_errors() is None
assert asdict(order) == {'name': 'pizza', 'sub_orders': None}
assert order.as_dict() == {'name': 'pizza'}
data = {'name': 'pizza', 'sub_orders': [{'name': 'pasta'}]}
order = Order(**data)
assert order.get_errors() is None
assert asdict(order) == {'name': 'pizza', 'sub_orders': [{'name': 'pasta'}]}
assert isinstance(order.sub_orders[0], SubOrder)
ValidatedDC - https://github.com/EvgeniyBurdin/validated_dc
I'm working on a project in which a dataclass holds information about a calculation. The class can be instantiated with only a few arguments, and the remaining arguments are filled in as the calculation proceeds. In the simplified example below, a user can make a Bicycle instance with only a model number and the code will fill in the rest as needed.
from typing import Optional
from pathlib import Path
from dataclasses import dataclass
@dataclass
class Bicycle:
'''Holds information about bikes'''
name: Optional[str] = None
model: Optional[str] = None
datasheet: Optional[Path] = None
def add_missing_data(bike: Bicycle) -> None:
'''Adds missing Bicycle information'''
if bike.datasheet is None:
bike.datasheet = get_datasheet(model=bike.model)
def get_datasheet(model: str) -> Path:
return Path(f'./unsorted_datasheets/{model}.pdf')Because Bicycle.model is set to None, Pylance gives me this error:
Argument of type "str | None" cannot be assigned to parameter "model" of type "str" in function "get_datasheet"
I can use type checking in the add_missing_data function to ensure that get_datasheet is not called if bike.model == None, but I'm unsure if I'm using dataclasses/Optional correctly. Is there a better solution than to add type checking in all the functions that modify a Bicycle instance?