Repository navigation
mypy not inferring that variable cannot be None after assignment from dict.get #4805
Description
Activity
Good research! I can also make the error go away through a temporary variable:
a = d.get("a", "foo bar") arg = a
I've got a feeling it's an bug in the type solver.
Reacted by Leah Einhorn, Anton Danilov, Marvin Kastner and Minjun Kim (Lapis0875)- addedbugmypy got something wrongmypy got something wrong
on Mar 27, 2018 - addedfalse-positivemypy gave an error on correct codemypy gave an error on correct code
on May 19, 2018 In my project, I have code like:
import typing class Task: def __init__(self, name: typing.Union[None, str] = None) -> None: self.name = name if not self.name: self.name = "Komu" def caps(self) -> str: return self.name.upper()
when I run mypy on it I get:
error: Item "None" of "Optional[str]" has no attribute "upper"I'm been forced to now right my code as:
class Task: def caps(self) -> str: # this assert is here to make mypy happy assert isinstance(self.name, str) return self.name.upper()
Reacted by J Kendal, Yukihiko Shinoda, Nicolai Skogheim, Menelaos Kotoglou, Grzegorz Adam Hankiewicz, hoho2b and Mark KuiackReacted by Nick Khitrov, piglei, Davide, Alexander Cabezas, matejmicek, xinyu, Fokko Driesprong, NaNAGISaSA, Yukihiko Shinoda, Shyamala Gopalakrishnan and 4 moreReacted by SonGokussj4@komuw That looks like a different problem. Can you open a new issue for it?
@komuw This is unrelated to this issue, and not actually a bug, mypy can't guess your intention for the ("long living" and externally visible)
.name. You should rewrite your code to make your intention explicit. For example:class Task: def __init__(self, name: Union[None, str] = None) -> None: if name is not None: self.name = name else: self.name = "Komu"
Or even simpler (assuming this is the actual code you wanted to type):
class Task: def __init__(self, name: str = "Komu") -> None: self.name = name
Reacted by Manuel and Gaurish Gangwarthanks for the pointer, re-writing as suggested makes mypy happy and is what I'll result to in my project.
I'm just surprised that mypy does not recognise that this two snippets are equivalent, and it should apply the same rules to both;self.name = name if not self.name: self.name = "Komu"
if name is not None: self.name = name else: self.name = "Komu"
and it should apply the same rules to both
No, it shouldn't.
Within the method body they result in same types for
self.name(you can check this withreveal_type()). But outside the method (and outside the class) mypy uses the first inferred type for instance variables. In this case it isOptional[str]in first snippet andstrin the second one.If we would infer
strfor instance variables this would cause errors in other places:task = Task() name: Optional[str] task.name = name # Should this be valid?
Since this is a backwards incompatible change with high potential for breaking currently passing code, there is no way this will be changed.
I'm having a similar issue, but with Callables.
def outer(func: Optional[Callable] = None) -> Callable: if func is None: func = lambda: None def inner(*args, **kwargs) -> Any: return func(*args, **kwargs) return inner
error: "None" not callable
As gvanrossum mentioned, the workaround of assigning it to a new variable makes the error go away.
def outer( func: Optional[Callable] = None, ) -> Callable: if func is None: func = lambda: None my_func = func def inner(*args, **kwargs) -> Any: '''Inner function''' return my_func(*args, **kwargs) return inner
No error
- added a commit that references this issue
on Dec 19, 2020 I've come across this issue (I believe).
Sharing a similar piece of code in case it helps in troubleshooting.
from typing import Optional KUBERNETES_REGISTRIES_ALIASES = { 'production': 'melchior-1.myorg.com', 'staging': 'balthasar-2.myorg.com', } kubernetes_registry: Optional[str] = 'casper-3.myorg.com' # from user input, may be None, an alias, or a full FQDN if kubernetes_registry: reveal_type(kubernetes_registry) # Revealed type is "builtins.str" kubernetes_registry = KUBERNETES_REGISTRIES_ALIASES.get(kubernetes_registry, kubernetes_registry) # resolve alias (if it is an alias), otherwise use it as is reveal_type(kubernetes_registry) # Revealed type is "Union[builtins.str, None]"With mypy 0.910
Hello, any update on this? I've encountered a very similar problem with
v0.910Reacted by Bas Nijholt, bdespond, Felix Bargfeldt, UnsafePointer<T>, Alex, Bradley Sherman, Keith Hughitt, Nathan Farmer and Ivan RevegukThe same thing happens if you guard against Nones with an exception:
def __init__(self, my_var_id: str, my_var: Optional[MyVar] = None): self._my_var = my_var if self._my_var is None: self._my_var = self._lookup_my_var(my_var_id) # Function that may retrieve a result i.e. from a DB if self._my_var is None: raise Exception("My var does not exist")
self._my_varwill be considered Optional in the rest of the code and produce theItem "None" of "Optional[MyVar]" has no attribute "..."error.Reacted by Kevin Fu, Taran Lu, Nick Rogers, Daniel Braun, Shyamala Gopalakrishnan, Leletir, Mose, Nathan Farmer and Jean-Frédéric GauronIn my case, I was certain that the instance is not
None(although the library that I use defined it asOptional[TYPE]).
My solution was to usetyping.castto the underlying type.# logger.parent is defined as Optional[logging.Logger] prefect_logger = cast(logging.Logger, logger.parent)
One can argue if this is elegant or not, but this is a solution I'm happy with (it silenced the FA).
Reacted by shawalli@shaybensasson this solution worked for me as well, when I knew that if I didn't return by a certain point my variable was guaranteed not to be None
@shaybensasson, rather than using a
cast, you might consider usingassert logger.parent is not None. This not only eliminates the type checking error, it also clearly documents your assumption in the code and provides runtime verification when running your program in debug mode.Reacted by Leah Einhorn, Samuel Carlsson, Andrei Korshikov and sigcyc- added a commit that references this issue
on Nov 22, 2022
Code
Expected output
None, since the code is correctly typed (afaict, at least)
Actual output
with
--strict-optional:Inserting
reveal_type(arg)before the print givesUnion[builtins.str, builtins.None].The following typechecks fine, so mypy definitely understands the return type of
dict.getwith two arguments:Similarly, mypy can infer that
argcannot be None after anis Noneand simple assignment:but combining the two fails.