Requirement:
I have a class with many fields initialized in __init__
method. Some of these fields should be possible to reset to initial values via a reset()
method.
I would like to provide typing info for these attributes and make Flake, MyPy, PyCharm (and me) happy with the solution.
Possible solutions:
Duplicate initial values
In this solution all tools (MyPy, Flake, PyCharm) are happy but not me. I have initial values in two places (__init__
and reset
) and I need to keep them in sync. There is a possibility that if in the future one initial value needs to be modified, then I will not change it in both places.
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.resetableCounter: int = 1 # the same value is in reset method. Keep them in sync!!!
# seven more attributes of different types and initial values
def reset(self) -> None:
self.resetableCounter = 1 # the same value is in __init__ method. Keep them in sync!!!
# reset remaining seven attributes to the same values as in __init__()
Keep initial values in one method only
The fix seems to be easy: keep initial values in reset
method only and call reset
from __init__
.
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.reset()
def reset(self) -> None:
self.resetableCounter: int = 1
# seven more attributes of different types and initial values
I'm happy (and Flake8 and MyPy too) with such solution but PyCharm complains that
Instance attribute resetableCounter defined outside __init__
This warning can be switched off, but sometimes it is useful - when I forgot to define some attribute in neither __init__
nor reset
method.
Define attributes as None
So we can improve the second solution - define attribute inside __init__
but set it as None
and then call the reset
method.
from typing import Optional
class Test:
def __init__(self) -> None:
self.persistentCounter: int = 0
self.resetableCounter: Optional[int] = None
# seven more attributes of different types - all defined as Optional
self.reset()
def reset(self) -> None:
self.resetableCounter = 1
# seven more attributes with different initial values
def foo(self) -> bool:
return self.resetableCounter > 10
The downside of this solution is that the attribute is defined as Optional[int]
, not int
and when I use it for example in the foo
method, then mypy complains
error: Unsupported operand types for < ("int" and "None")
note: Left operand is of type "Optional[int]"
This can be fixed when I put an additional assert inside foo
method:
def foo(self) -> bool:
assert self.resetableCounter is not None
return self.resetableCounter > 10
It makes all tools happy but not me - I do not want to fill the source code with many "unnecessary" assertions.
Question:
How to fulfill the requirement specified above and mitigate downsides of presented solutions?
Instance attribute attribute_name defined outside __init__ describes a similar issue but no answer fits my requirement (see above).
Be inspired by @Wombatz response I ended with this solution:
class Test:
resetableCounter: int = 1
# seven more attributes of different types
def __init__(self) -> None:
self.persistentCounter: int = 0
self.reset()
def reset(self) -> None:
if 'resetableCounter' in self.__dict__:
del self.resetableCounter
# del also rest seven attributes
All tools are happy and the code is clean, brief and readable.
Many thanks for all contributions.
Update
I had to add the if 'resetableCounter' in self.__dict__:
line to the code above. Otherwise the reset
method failed when it was called from __init__
or when it was called two times in the row without variable modification between first and second call.
The code is not so nice now (especially the need to write manually the variable name as string) but I think it is still the best (known to me) solution.