Strategies for Optional attributes that become non-Optional during runtime? #838
-
You might have a class with an attribute that you want to accept from dataclasses import dataclass
from typing import Optional
@dataclass
class MyClass:
val: str
key: Optional[int] = None
def get_objs():
objs = [MyClass("b"), MyClass("a")]
for index, obj in enumerate(sorted(objs, key=lambda o: o.val)):
obj.key = index
return objs
objs = get_objs()
print(objs) # [MyClass(val='b', key=1), MyClass(val='a', key=0)]
objs[0].key + 1 # typing error: can't add to None In this case anything using There are workarounds, such as:
These are all sort of hacking around the type system though and may not be available or have other problems. Any thoughts or general strategies on dealing with this? Thanks. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 8 replies
-
I don't think the type system provides any direct way to do what you want here, and it's hard to imagine a clean way to add something like this. Most type checkers should support something like Personally I would try to rewrite this code so that the |
Beta Was this translation helpful? Give feedback.
-
I think this is hard to do in the current type system. Usually I try to follow @JelleZijlstra's advice, since I'm a big fan of immutable object. But if that's not reasonable to do, I would probably define I also like the solution that you hinted at, where you define a private class MyClass:
def __init__(self, val: str, key: int | None = None) -> None:
self._key = key
@property
def key(self) -> int:
if self._key is None:
raise RuntimeError("key not set")
return self._key
@key.setter
def key(self, key: int) -> None:
self._key = key If there isn't already a custom property that does something like that in the standard library, it should be fairly easy to create one to avoid most of the boilerplate. |
Beta Was this translation helpful? Give feedback.
-
It is not really a good idea to use mutable structures and typing together. Especially in cases when you change your type: in this case you are changing But, you can still express this with Python's type system and three hacks: from dataclasses import dataclass
from typing import Optional, Generic, TypeVar, List, cast
_PayloadType = TypeVar('_PayloadType')
@dataclass
class _MyClass(Generic[_PayloadType]):
val: str
# NOTE: hack1
key: _PayloadType = None # type: ignore
# NOTE: hack2
MyClass = _MyClass[Optional[int]]
MyFullClass = _MyClass[int]
def get_objs() -> List[MyFullClass]:
objs = [MyClass("b"), MyClass("a")]
for index, obj in enumerate(sorted(objs, key=lambda o: o.val)):
obj.key = index
# NOTE: hack3
return cast(List[MyFullClass], objs)
objs = get_objs()
objs[0].key + 1 # ok Hacks:
It works 🎉 But I am not really happy with this code. Here's an alternative: from dataclasses import dataclass
from typing import Optional, List
@dataclass
class MyClass:
val: str
@dataclass
class MyEmptyClass(MyClass):
key: Optional[int] = None
@dataclass
class MyFullClass(MyClass):
key: int
def get_objs() -> List[MyFullClass]:
objs = [MyEmptyClass("b"), MyEmptyClass("a")]
new_objs = []
for index, obj in enumerate(sorted(objs, key=lambda o: o.val)):
new_objs.append(MyFullClass(val=obj.val, key=index))
return new_objs
objs = get_objs()
objs[0].key + 1 # ok It is cleaner, but uses two lists and copies extra data. It is all about trade-offs. |
Beta Was this translation helpful? Give feedback.
-
I agree with everyone that letting the attribute just be However, perhaps a feature request to the type system would be to allow for attribute-level casts, such as |
Beta Was this translation helpful? Give feedback.
-
I've been pursuing making data classes that satisfy mypy for external packages without having to |
Beta Was this translation helpful? Give feedback.
I don't think the type system provides any direct way to do what you want here, and it's hard to imagine a clean way to add something like this. Most type checkers should support something like
assert obj.key is not None
, but that gets repetitive.Personally I would try to rewrite this code so that the
key
attribute is known when the object is initialized.