Skip to content
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

'<nothing>' object is not iterable #10233

Closed
DevilXD opened this issue Mar 21, 2021 · 8 comments
Closed

'<nothing>' object is not iterable #10233

DevilXD opened this issue Mar 21, 2021 · 8 comments
Labels
bug mypy got something wrong

Comments

@DevilXD
Copy link

DevilXD commented Mar 21, 2021

Bug Report

The internal <nothing> object not being an iterable appears to be a problem in a case like the example below.

To Reproduce

from typing import List, Dict, Tuple, NamedTuple, Callable, TypeVar


MenuReturn = TypeVar("MenuReturn")
MenuFunction = Callable[[], MenuReturn]


class MenuOption(NamedTuple):
    description: str
    function: MenuFunction


def menu(options: List[MenuOption]) -> MenuReturn:
    options_dict: Dict[str, MenuFunction] = {}
    for num, entry in enumerate(options, start=1):
        print(f"{num}) {entry.description}")
        options_dict[str(num)] = entry.function
    while True:
        key = input("Pick an option: ")
        if key in options_dict:
            break
    return options_dict[key]()


def func1() -> Tuple[bool, bool]:
    return (False, False)


def func2() -> Tuple[bool, bool]:
    return (True, True)


results: Tuple[bool, bool] = menu([  # need type annotation for 'results' - fine, I can just add it here
    MenuOption("Return two False values", func1),
    MenuOption("Return two True values", func2),
])
print(results)

# Define the types explicitly
result1: bool
result2: bool
result1, result2 = menu([  # '<nothing>' object is not iterable - types being defined doesn't matter
    MenuOption("Return two False values", func1),
    MenuOption("Return two True values", func2),
])
print(result1, result2)  # the types are defined, but the error above remains

Expected Behavior

It'd let me at least define the types of result1 and result2 before the menu() usage, silencing the '<nothing>' object is not iterable error.

Actual Behavior

This is not the case, and while result1 and result2 do end up with the correct types, I still need to add # type: ignore on the menu() usage line.

Your Environment

  • Mypy version used: 0.812
  • Mypy flags: none used
  • Python version used: 3.8.8
  • Operating system and version: Win7x64

Notes

It seems that MyPy ends up with the MenuReturn type var being assigned to a list of whatever each function returns, those being Tuple[bool, bool] from func1 and Tuple[bool, bool] from func2. Normally, I'd expect it to go with a union of those two types, that'd collapse to just Tuple[bool, bool] - if the types would be different, for example str and int, it'd end up as Union[str, int].

@DevilXD DevilXD added the bug mypy got something wrong label Mar 21, 2021
@LyonsDo
Copy link

LyonsDo commented Mar 22, 2021

Not sure if this is the same issue but I'm getting "TypeError: 'str' object is not callable" for the string s in
@docstrings.dedent(s, stacklevel=3)

Your Environment

PyCharm 2020.3.4
Python version used: 3.9.2
Operating system and version: Win10x64

@JelleZijlstra
Copy link
Member

@LyonsDo pycharm errors shouldn't be reported to mypy; it's a different type checker.

@DevilXD I think your problem is that NamedTuples can't be generic (#685). If you turn on the --disallow-any-generics flag (as you probably should), it will tell you about a few missing instances of [T]; some of those can be fixed but the NamedTuple cannot.

@LyonsDo
Copy link

LyonsDo commented Mar 22, 2021

Sorry. I have the mypy plugin enabled.

@DevilXD
Copy link
Author

DevilXD commented Mar 23, 2021

@JelleZijlstra I believe there's a different issue at play here, because getting rid of the NamedTuple and using just a normal tuple instead, generates the same error.

from typing import List, Dict, Tuple, Callable, TypeVar


MenuReturn = TypeVar("MenuReturn")
MenuFunction = Callable[[], MenuReturn]


def menu(options: List[Tuple[str, MenuFunction]]) -> MenuReturn:
    options_dict: Dict[str, MenuFunction] = {}
    for num, entry in enumerate(options, start=1):
        print(f"{num}) {entry[0]}")
        options_dict[str(num)] = entry[1]
    while True:
        key = input("Pick an option: ")
        if key in options_dict:
            break
    return options_dict[key]()


def func1() -> Tuple[bool, bool]:
    return (False, False)


def func2() -> Tuple[bool, bool]:
    return (True, True)


results: Tuple[bool, bool] = menu([  # need type annotation for 'results' - fine, I can just add it here
    ("Return two False values", func1),
    ("Return two True values", func2),
])
print(results)

# Define the types explicitly
result1: bool
result2: bool
result1, result2 = menu([  # '<nothing>' object is not iterable - types being defined doesn't matter
    ("Return two False values", func1),
    ("Return two True values", func2),
])
print(result1, result2)  # the types are defined, but the error above remains

@JelleZijlstra
Copy link
Member

You're not setting generic parameters fully. Try using --disallow-any-generics.

@LyonsDo
Copy link

LyonsDo commented Mar 23, 2021 via email

@DevilXD
Copy link
Author

DevilXD commented Mar 24, 2021

Huh, alright, I got it to work. This is what I've got:

from typing import List, Dict, Tuple, Callable, TypeVar


MenuReturn = TypeVar("MenuReturn")
MenuFunction = Callable[[], MenuReturn]


def menu(options: List[Tuple[str, MenuFunction[MenuReturn]]]) -> MenuReturn:
    options_dict: Dict[str, MenuFunction[MenuReturn]] = {}
    for num, entry in enumerate(options, start=1):
        print(f"{num}) {entry[0]}")
        options_dict[str(num)] = entry[1]
    while True:
        key = input("Pick an option: ")
        if key in options_dict:
            break
    return options_dict[key]()


def func1() -> Tuple[bool, bool]:
    return (False, False)


def func2() -> Tuple[bool, bool]:
    return (True, True)


results = menu([
    ("Return two False values", func1),
    ("Return two True values", func2),
])
reveal_type(results)

result1, result2 = menu([
    ("Return two False values", func1),
    ("Return two True values", func2),
])
reveal_type(result1)
reveal_type(result2)

I'm guessing this isn't a real bug then. However, the error message of <nothing> object is not iterable is quite confusing. Perhaps this could be improved?

@JelleZijlstra
Copy link
Member

Agree that this is unintuitive! #10241 seems like the underlying issue, so let's keep that one open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

3 participants