-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
When using dict.get
with a default value, it is expected that the key may not be found. Most of the time it's because we're not dealing with literal types, so we can't ensure that a key is part of the dict. But other times, it's we explicitely use a value that has a type we know won't be in range (often None).
See the example below (it is of course minimal and abstracted from a real-world example, but should still show the use):
code_events = {
0: "ZERO_EVENT",
1: "ONE_EVENT",
# ...
127: "LAST_EVENT",
}
def parse_foo_code(self, code: int | None):
code = code and (code & 0x7f) # Some bitwise operation to skip bit 8, this just shows we can't use -1 as default. And who knows what other keys could be used in the dict
event = code_events.get(code, "SPECIAL_DEFAULT_FOO_EVENT")
# ... do more with the event
def parse_bar_code(self, code: int | None):
code = code and (code & 0x7f) # Some bitwise operation to skip bit 8
event = code_events.get(code, "SPECIAL_DEFAULT_BAR_EVENT")
# ... do more with the event
Currently it has to be written as such (without a cast). Which adds redundancy checks and no type-safety:
def parse_foo_code(self, code: int | None):
code = code and (code & 0x7f) # Some bitwise operation to skip bit 8
if isinstance(code, int): # You could imagine this could be more complex than just int. Maybe it can't even be easily expressed
event = code_events.get(code, "SPECIAL_DEFAULT_FOO_EVENT")
else:
event = "SPECIAL_DEFAULT_FOO_EVENT"
# ... do more with the event
What if this definition in dict:
@overload
def get(self, __key: _KT) -> _VT | None: ...
@overload
def get(self, __key: _KT, __default: _VT | _T) -> _VT | _T: ...
was changed to:
@overload
def get(self, __key: _KT) -> _VT | None: ...
@overload
def get(self, __key: object, __default: _VT | _T) -> _VT | _T: ... # Either object, or Any if it causes variance issue.
What are your thoughts? Any reason this would be a bad idea? From what I can see there are legit sensible use-cases, it stays type-safe, and allows for more concise code. Or just use a cast and call it a day?