python dataclasses with optional attributes
Question:
How do you make Optional attr’s of a dataclass?
from dataclasses import dataclass
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: # what to write here?
kennys_stuff = {
'knife': True,
'fork': True
}
print(CampingEquipment(**kennys_stuff))
I tried field(init=False)
, but it gave me:
TypeError: CampingEquipment.__init__() missing 1 required positional argument: 'missing_flask_size'
By Optional I mean __dict__
may contain the key "missing_flask_size" or not. If I set a default value then the key will be there and it shouldn’t be in some cases. I want to check its type if it is there.
I tried moving the field(init=False)
to the type location (after the colon) so I could make it more explicit as to the thing I wanted optional would be the key and not the value.
So I want this test to pass:
with pytest.raises(AttributeError):
ce = CampingEquipment(**kennys_stuff)
print(ce.missing_flask_size)
Answers:
Just add a value to optional field
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: int = field(default=None)
# CampingEquipment(knife=True, fork=True, missing_flask_size=None)
See this post too How to create an optional field in a dataclass that is inherited?
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
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:
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(CampingEquipment(**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).
I was also looking for the same and find a workaround. Simply pop the item that you don’t want if conditional. like this
@dataclass
class Element:
type: str
src: str
scale: Scale
duration: float = -1
start: float = 0
extra_time: float = 0
cache: bool = True
def get_data(self) -> None:
if self.type != "image":
self.__dict__.pop('scale')
return self.__dict__
if self.type == "image":
return self.__dict__
Simply pop the item that you don’t want with conditional and call this method get_data(). Hope this will help.
How do you make Optional attr’s of a dataclass?
from dataclasses import dataclass
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: # what to write here?
kennys_stuff = {
'knife': True,
'fork': True
}
print(CampingEquipment(**kennys_stuff))
I tried field(init=False)
, but it gave me:
TypeError: CampingEquipment.__init__() missing 1 required positional argument: 'missing_flask_size'
By Optional I mean __dict__
may contain the key "missing_flask_size" or not. If I set a default value then the key will be there and it shouldn’t be in some cases. I want to check its type if it is there.
I tried moving the field(init=False)
to the type location (after the colon) so I could make it more explicit as to the thing I wanted optional would be the key and not the value.
So I want this test to pass:
with pytest.raises(AttributeError):
ce = CampingEquipment(**kennys_stuff)
print(ce.missing_flask_size)
Just add a value to optional field
@dataclass
class CampingEquipment:
knife: bool
fork: bool
missing_flask_size: int = field(default=None)
# CampingEquipment(knife=True, fork=True, missing_flask_size=None)
See this post too How to create an optional field in a dataclass that is inherited?
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
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:
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(CampingEquipment(**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).
I was also looking for the same and find a workaround. Simply pop the item that you don’t want if conditional. like this
@dataclass
class Element:
type: str
src: str
scale: Scale
duration: float = -1
start: float = 0
extra_time: float = 0
cache: bool = True
def get_data(self) -> None:
if self.type != "image":
self.__dict__.pop('scale')
return self.__dict__
if self.type == "image":
return self.__dict__
Simply pop the item that you don’t want with conditional and call this method get_data(). Hope this will help.