-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mypy doesn't distinguish between a bool
and an int
?
#14255
Comments
You've annotated your function as returning bool | int, so mypy is right to complain. The expression could be int, but your variable is bool. If your question is why mypy says just int instead of bool | int, that's because bool is a subclass of int at runtime, so bool | int is basically equivalent to int |
So, how should I annotate the return type(s) of my My function can, indeed, return an I know that the What do you suggest? |
Personally, I'd return Any. If you want to be type safe, you could also return a Union like currently, but add asserts. If your names are constants, overloads involving literals can achieve type safety without asserts. |
Can you please provide an example what you mean by this? |
Obviously this doesn't scale well if you have many of these, which is why something like this I'd probably consider returning an object somewhere. Polymorphic return types are a trap in the annotations world. |
Are you saying I should better just annotate the return type of my function as
|
Here are some alternatives that can help make your code more robust. You could create separate wrapper functions for def load_bool_setting(table_name: str, key_name: str) -> bool:
setting = load_settings(table_name, key_name)
if type(setting) != bool:
raise ValueError(f"key {key_name} was not a bool as expected")
return setting Or you could make your original function generic so it handles any setting type: T = TypeVar("T")
def load_typed_setting(table_name: str, key_name: str, expected_type: type[T]) -> T:
with open(constants.SETTINGS_FILE_PATH, "rb") as settings_file:
setting = tomllib.load(settings_file)
if type(setting) != expected_type:
raise ValueError(f"key {key_name} was not a {expected_type} as expected")
return setting |
I have now defined my def load_setting(group: str, name: str) -> bool | int:
"""Return a setting value by the given group and name."""
with open("settings.toml", "rb") as settings_file:
settings = tomllib.load(settings_file)
return settings[group][name] The usage of my load_setting("engine", "pondering") My
When I call my Now, I can't, for the life of me, explain why (oh why?!) does mypy complain about the return type of my However, mypy does not complain if I pass the bool(load_setting("engine", "pondering")) And only now is mypy finally satisfied. But having to use Python's Can anyone explain to me why is mypy not capable of handling the union type of a |
Mypy is enforcing the normal rules in the type system. Your function is annotated to return either a If you pass an |
It won't be accurate to annotate the return type of my Let's see two uses of my load_setting("engine", "pondering") # The return value is: False (i.e., a bool)
load_setting("clock", "time") # The return value is: 60 (i.e., an int) I am passing the result of my But, strangely enough, if I pass my Why does mypy not complain in the case of |
Yes.
Yes, this is how it usually is.
Same for If you had an value = load_settings("foo", "bar")
third_party_which_expects_str(value) would detect a typing issue on the 2nd line? |
Yes, @ikonst, I know this. I want to know why do I need to use Python's built-in |
It sounds like you are essentially doing this:
Mypy obviously should complain about this: an int is not a bool. |
Your code looks like this (1): value = load_setting("engine", "pondering")
third_party_which_expects_bool(value) not like this (2): third_party_which_expects_bool(False) Try changing it to (2) and then mypy wouldn't "complain", right? ... And yes, of course, of course, that's not a change you want to make, since you don't want to pass a constant. You want to pass a value that's only known at runtime. What mypy knows at "check-time" is that it's some value that's either One thing that's confusing here is that an |
@ikonst, yes, that's the problem with mypy: it can't process types at runtime. But, interestingly enough, whenever you use I always thought that using type annotations would make my codebase so much more readable, but now I realize I have to fight really hard to adhere to mypy, and that is making my work 10 times harder because I need to painstakingly make sure to make mypy happy in every step. That's like having a very demanding wife. 🤣 |
The mypy error I get is this: The As I am passing the result of my What is going on here? Does mypy have a bug in not being able to distinguish between a |
mypy doesn't know what your function returns at runtime. mypy knows it returns |
@PedanticHacker, I think you're confused about the purpose of a static type checker. You may find it useful to read the introduction portion of the mypy documentation. Static type checkers do not "run" your code. They don't evaluate "values". That's the job of the Python interpreter. Static type checkers evaluate "types" of expressions within your code and detect potential bugs in your code by looking for type violations. A "type" represent a class of potential values. A static type checker doesn't know (or care) that a particular invocation of a function returns the value Static type checking is completely optional in Python. If you are not finding value with static type checking, you can simply omit the type annotations and refrain from running mypy. |
Guys, I completely understand what you want to tell me. But please consider these 2 very important factors:
Why is using the built-in |
Because |
@JelleZijlstra, please know that the return type of |
The return type is |
@JelleZijlstra, I explicitly declare that my |
It's not even necessarily about these two particular types. It's just how Unions in return types work: type checkers correctly assume the worst case that you can return any of the types in the Union. The following program is buggy. mypy's behaviour ensures that you're alerted to this fact:
|
@hauntsaninja, how should I annotate my function, then? |
Myself and others already answered that at the top of this thread ;-) #14255 (comment) Returning Any would look like:
Using asserts could look like: #14255 (comment) Using overload + literals could look like: #14255 (comment) |
Will mypy ever check types at runtime? |
mypy will never run your code. The Python interpreter will never check types at runtime (unless you write code that does those checks). |
I have a feeling that having I immediately come into conflict with mypy when my Is there just one and only one obvious way (not three!) to go about it? Something Pythonic, but avoiding the |
This is still a problem in mypy 1.8.0. How come that I just can't annotate a function/method that returns either an I have a
And I have a function to get the value of a certain option: def get_config_value(section: str, option: str) -> int | bool | None:
"""Get the config value of an `option` from the given `section`."""
config_parser = ConfigParser()
config_parser.read("rexchess/config.ini")
if section == "clock":
return config_parser.getint(section, option)
elif section == "engine":
return config_parser.getboolean(section, option)
else:
return None Then I have a property in my Game class... class Game:
"""An implementation of the standard chess game."""
def __init__(self) -> None:
self._engine_plays_as_black: bool = get_config_value("engine", "black")
@property
def engine_plays_as_black(self) -> bool:
return self._engine_plays_as_black The complaint from mypy I get for the
Can anyone explain to me why is mypy so narrow minded? |
MyPy is correct there. Your function returns T = TypeVar("T", int, bool, str, float) # etc
class Option(Generic[T]):
def __init__(self, kind: type[T], section: str, name: str) -> None: ...
def get(option: Option[T]) -> T: ....
PLAY_AS_BLACK = Option(bool, "engine", "black")
get(PLAY_AS_BLACK) # bool In get(bool, "engine", "black") |
Some time has now passed since I originally asked this question. We now have Python 3.12, which introduces So, like this:
I know that I'd like your expert opinion on what you think would happen. Would I stumble upon the same issue as with the return type annotation |
That won't change anything no. |
I understand. So, how would you suggest I go about this? I haven't find any information anywhere regarding an alternative to the return type annotation The main issue is that It's strange that in Python We need a unique type annotation, something like |
The subtype relationship between Your code right now only works because it knows that certain settings produce integers, and others produce booleans. You need to use generics or overloads to make the return type vary depending on the settings names. One way is to change your API to instead have a bunch of Option objects, which I think is better anyway. That prevents typos with settings names elsewhere in code, and lets you loop over all possible options. Alternatively you could add a lot of overloads to the function using |
I think that the most straightforward way would be to create When will mypy recognize |
There will be provisional support in the next release. |
Excellent! And when will the next release be available? |
I have encountered a strange mypy error.
My function is defined like this:
And my variable is defined like this:
But the error mypy outputs is this:
The version of mypy used is 0.991. The operating system used is Windows 11 64-bit (updated to the latest version possible), running on Python 3.11. This error appears in Sublime Text 4 (its latest version of 4143) with the mentioned version of mypy and also in VSCode (also updated to the latest version possible and also updated its mypy extension to the latest version of 0.991 [at the time of writing this]).
So, tell me guys: WTF is going on here?
The text was updated successfully, but these errors were encountered: