I’ve got a situation, and the ‘obvious’[1] solution would be a Sentinal class with attributes.
Context:
We’ve got a code system where depending on the settings, some parts of the code might or might not run. So for example, we have
class Settings():
coloc: ColocationSettings | None
and code that requires the value of settings.coloc.forecast only makes sense to run when colocation is enabled, so such code blocks are behind if settings.coloc is not None: guards. Which both makes sense and keeps the type-checker happy.
Now the issue is an array of SettingsOverrides that is read from a collection of csv files.
We define loosely speaking
period_settings[t] = combine(settings, settings_overrides[t])
and it is an invariant in our system that period_settings[t].coloc is None if and only if settings.coloc is None.
But accessing period_settings[t].coloc.forecast makes the type-checker angry, even if I do it inside a if settings.coloc is not None: guard.
What I want to do
The solution I have in mind is to create a sentinel, and type PeriodSettings as
class PeriodSettings():
coloc: ColocationSettings | NULL
where NULL will have (for example) the forecast attribute, at least as far as the type checker is concerned. (If that attribute were accessed at runtime, I’m quite happy for it to raise an error.)
This would replace a significant number of cast() and assert statements.
The best I can come up with myself
@dataclass
class ColocationSettings:
forecast: str
max_export: float
class ColocNULL:
# No init, fields remain uninitialised
forecast: str
max_export: float
COLOC_NULL = ColocNULL()
@dataclass
class PeriodSettings:
coloc: ColocationSettings | ColocNULL
I think that gets me most of what I want and probably everything I need. But the PEP 661 Sentinels do have some nice properties that I’d like.
to me ↩︎