pytype icon indicating copy to clipboard operation
pytype copied to clipboard

Improve inference by enabling `--protocols` by default.

Open gpshead opened this issue 8 years ago • 3 comments

The example below is inferring amount as Iterable and claiming that balance is an unknown attribute as well as claiming that Account.deposit returns a list.

Potentially multiple different issues. Or just a need for actual documentation to describe the proper getting started code base bootstrapping process and expected odd results on entirely un-annotated code. :)

#!/usr/bin/python3

from typing import Dict, Optional, Union


class Account:
    """Only a horrible bank uses a float for account balances."""
    def __init__(self, opening_balance: float = 0.0):
        self.balance = opening_balance  # expect float

    def deposit(self, amount):  # expect (float) -> float
        self.balance += amount
        return self.balance


class Bank:
    def __init__(self, bank_owner="Unladen Swallows"):
        self._accounts = {}  # expect Dict[str, Account]
        self._accounts[bank_owner] = Account(3.5)

    def deposit(self, name, amount):  # expect (float) -> Optional[float]
        """Multiple return types is a horrible API!"""
        account = self._accounts.get(name)
        if account:
            return account.deposit(amount)
        else:
            return None


def main():
    bank = Bank()
    balance = bank.deposit("Unladen Swallows", 5)
    print("balance:", balance)
    assert balance == 8.5


if __name__ == '__main__':
    main()
rainbow_unicorn$ pytype -V 3.5 --output - example.py 
from typing import Any, Dict, Iterable, Optional

class Account:
    __doc__ = ...  # type: str
    balance = ...  # type: Any
    def __init__(self, opening_balance: float = ...) -> Any: ...
    def deposit(self, amount: Iterable) -> list: ...

class Bank:
    _accounts = ...  # type: Dict[Any, Account]
    def __init__(self, bank_owner = ...) -> None: ...
    def deposit(self, name, amount: Iterable) -> Optional[list]: ...


def main() -> None: ...
File "example.py", line 12, in deposit: No attribute 'balance' on Account [attribute-error]

gpshead avatar May 20 '17 22:05 gpshead

This seems to only happen under Python 3. Under Python 2 (with backported type annotations), the inference looks correct, i.e., balance and amount are, and deposit returns, a Union[complex, float].

In general, our Python 3 compatibility is still WIP.

matthiaskramm avatar May 22 '17 19:05 matthiaskramm

(CC @ddfisher :-)

gvanrossum avatar May 22 '17 22:05 gvanrossum

This no longer seems to be Python 3-specific, since I now get identical results in 2 and in 3:

from typing import Any, Dict

class Account:
    __doc__ = ...  # type: str
    balance = ...  # type: Any
    def __init__(self, opening_balance: float = ...) -> None: ...
    def deposit(self, amount) -> Any: ...

class Bank:
    _accounts = ...  # type: Dict[Any, Account]
    def __init__(self, bank_owner = ...) -> None: ...
    def deposit(self, name, amount) -> Any: ...


def main() -> None: ...

The incorrect Iterable and list are gone, but balance is still Any. That's happening because we stopped trying to infer parameter types, so doing self.balance += amount, where amount is an unknown, causes the type of balance to become unknown as well.

Passing the --protocols flag gets us more precise types:

from typing import Any, Dict, Optional, Union

class Account:
    __doc__ = ...  # type: str
    balance = ...  # type: Union[complex, float]
    def __init__(self, opening_balance: float = ...) -> None: ...
    def deposit(self, amount: complex) -> Union[complex, float]: ...

class Bank:
    _accounts = ...  # type: Dict[Any, Account]
    def __init__(self, bank_owner = ...) -> None: ...
    def deposit(self, name, amount: complex) -> Optional[Union[complex, float]]: ...


def main() -> None: ...

So I think the way to fix this would be to make --protocols the default.

rchen152 avatar Sep 26 '18 23:09 rchen152