Improve inference by enabling `--protocols` by default.
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]
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.
(CC @ddfisher :-)
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.