Considering the following program:
from decimal import Decimal
from typing import Union
def foo(bar: Union[Decimal, int]):
print(Decimal(1) + bar)
print(bar + Decimal(1))
Why does mypy complain in the second print()?
$ mypy foo.py
foo.py: note: In function "foo":
foo.py:6: error: Unsupported operand types for + ("Union[Decimal, int]" and "Decimal")
I'm using mypy 0.3.1 on Python 3.5.1, Ubuntu 16.04.
EDIT: This seems to be a bug in mypy.
Related
I am relatively new to Python. Using Python 3.7 in this example below. The Linter is not catching any of the coding errors nor, it throws any exception when wrong return types are returned. What is the best and formal way of handing such issues?
from typing import Tuple
from abc import ABC, abstractmethod
class MyAbc(ABC):
#abstractmethod
def get_hello(self) -> Tuple[bool, str, str]:
# Need the return to be a Tuple of 3 values: bool, str, str
pass
class ImplementedClass(MyAbc):
def get_hello(self):
return True, "Hello"
# But in the implementation I am returning only 2 values: bool, str
# This coding error is not caught here
ic: MyAbc = ImplementedClass()
print(ic.get_hello()) # Error escaped
resp1, resp2, resp3 = ic.get_hello()
# The issue is caught only here
# Pylint: Possible unbalanced tuple unpacking with sequence defined at line 15: left side has 3 label(s), right side has 2 value(s)
print(resp1, resp2, resp3)
print(ImplementedClass().get_hello())
def three_returns() -> Tuple[str, str, str]:
return "one", "two"
print(three_returns()) # Error escaped
def something(data: str) -> str:
print(type(data), data)
return 1 # Supposed to return str, returning int, not caught
value: str = something(2) # Expected str but int returned
print(value.upper()) # Pylint: Instance of 'int' has no 'upper' member
As mentioned in the code block, when incorrect object is returned, pylint or python will never throw any error. It is only when it is explicitly mapped or any class methods are invoked like str.upper() that's when the error is thrown. This would lead to testing all the paths thoroughly else, it can be sure that code block would work.
Is it how it is and we have live with it or there any better ways to handle it like what we get compile time errors in C++, Java?
What you seem to be searching for is a type checker like mypy, pyright or pyre.
I was hoping to use mypy's static duck typing facility to write a function that can process a sequence of number-like objects, where "number-like" is defined by numbers.Number:
from numbers import Number
from typing import Sequence
def something_numerical(xs: Sequence[Number]) -> Number:
...
print(something_numerical([1., 2., 3.]))
However, when I call this code with a list of floats or ints, I get a mypy error:
$ print(multiply([1., 2., 3.]))
foo/foo.py:9: error: List item 0 has incompatible type "float"; expected "Number"
foo/foo.py:9: error: List item 1 has incompatible type "float"; expected "Number"
foo/foo.py:9: error: List item 2 has incompatible type "float"; expected "Number"
I realize that the float type is not a subclass of numbers.Number. However, the numbers module provides a set of abstract base classes that are intended to be used to check whether an object has the requisite methods to do numerical operations. How might I rewrite this code so that (1) it can still process ints, floats, fractios.Fraction, and so on, and (2) so that it passes type checking by mypy?
The answer by #SUTerliakov is informative and accurate; however, while working on this problem I came across another way of solving the problem that I will post here for others who encounter this. I simply defined my own "Number" protocol from scratch:
from typing import Protocol
class Number(Protocol):
def __abs__(self) -> 'Number':
...
def __mul__(self, Number) -> 'Number':
...
def __add__(self, Number) -> 'Number':
...
def __lt__(self, Number) -> bool:
...
def __gt__(self, Number) -> bool:
...
def __truediv__(self, Number) -> 'Number':
...
def __rtruediv__(self, Number) -> 'Number':
...
Here the choice to include __mul__, __abs__, __truediv__ and so on were based on the operations I needed to perform inside my function, rather than based on any abstract notion of what a number ought or ought not to be.
It seems that the "number" concept in python is quite complicated and hard to fit all cases, so it really might make logical sense to define a per-project protocol like this.
It was stated in #bzu answer, but I'd like to add some explanation to it.
First thing to note: issubclass(int, Number) and issubclass(float, Number) both evaluate to True. This is very surprising type-checking behavior, but it was standardized in PEP484:
Rather than requiring that users write import numbers and then use numbers.Float etc., this PEP proposes a straightforward shortcut that is almost as effective: when an argument is annotated as having type float, an argument of type int is acceptable; similar, for an argument annotated as having type complex, arguments of type float or int are acceptable. This does not handle classes implementing the corresponding ABCs or the fractions.Fraction class, but we believe those use cases are exceedingly rare.
So to support built-in numbers you can use just int, float or complex. To handle other ABC's you should use appropriate numbers member. I don't know why float was not made compatible with numbers.Number.
For almost all cases you can use a type alias (TypeAlias was backported with typing_extensions module for python<3.10):
from fractions import Fraction
from numbers import Number
from typing import TypeAlias
AnyNumber: TypeAlias = Number | float
def f(x: AnyNumber) -> bool:
return x == 0
f(1)
f(1.0)
f(Fraction(1, 3))
This typechecks. One incompatible class I'm aware of is decimal.Decimal: it is not compatible (it would be expected, if Number were made compatible with float, because Decimal is not and Decimal(1) / 2 fails - but it is not the case, as we'll see later).
If your function uses AnyNumber and int together, everything dies:
def f(x: AnyNumber) -> float:
return x / 2 + 1 # E: Unsupported operand types for / ("Number" and "int")
Although you can, for example, do Fraction(1,2) / 2, Number does not guarantee int or float compatibility. You can use numbers.Real or numbers.Complex instead - they are compatible with float:
AnyReal: TypeAlias = Real | float
This allows x / 2 + 1 and remains incompatible with decimal.Decimal, but now it is intended behavior.
You can use this playground to investigate the topic further. Also having look at numbers in typeshed may help.
It seems using Union will be the way to go:
from numbers import Number
from typing import Sequence, Union
from fractions import Fraction
def something_numerical(xs: Sequence[Union[Number, float]]) -> Union[Number, float]:
return sum(xs)
if __name__ == '__main__':
print(something_numerical([1.2, 2, Fraction(1, 2)]))
I want to calculate (π/4)^2-sin(π/4) in python:
import math
a=((math.pi)^2/16)-math.sin(math.pi/4)
print(a)
It gives the error:
TypeError: unsupported operand type(s) for ^: 'float' and 'float'
I also tried using
a=float(((math.pi)^2/16)-math.sin(math.pi/4))
But still doesn't work
^ is not supported in python. Instead, you can use math.pow.
import math
a=(math.pow((math.pi),2)/16.0)-math.sin(math.pi/4)
print(a)
^ is not supported in python. Instead, you can use math.pow or **
math.pow(math.pi,2)
math.pi ** 2
No sense in evaluating pi/4 twice.
import math
pi4 = math.pi/4
a = pi4**2 - math.sin(pi4)
Using mypy to statically check some of my code and got this problem. Code example:
import csv
d: csv.Dialect = csv.excel
d = csv.Sniffer().sniff("a")
but mypy gives this error in the first assignment to d:
error: Incompatible types in assignment (expression has type "Type[excel]", variable has type "Dialect")
So the natural fix for this is to change the type of the variable.
from typing import Type
import csv
d: Type[csv.Dialect] = csv.excel
d = csv.Sniffer().sniff("a")
But now I get this error on the second assignment to d:
error: Incompatible types in assignment (expression has type "Dialect", variable has type "Type[Dialect]")
But this is odd because csv.excel is a valid return for the sniff function so surely they must have the same type.
Python 3.7.3, mypy 0.701
I think this is a bug in the typeshed: I've raised an issue
According to typeshed, Sniffer.sniff returns a value of type csv.Dialect whereas in fact it returns a value of type Type[csv.Dialect]
Since I discovered type hints in Python, I started using them, because I think they are useful for the reader of the code. I'm looking forward that these type hints will eventually become type static checkers in the future, because it would make code a lot more reliable, and that's very important for a serious project.
Apart from that, I've a lot of functions where the return type can either be BSTNode or None. It can be None for example because in a search function, if no BSTNode is found, None is simply returned. Is there a way of specifying that the return type could also be None?
Currently I'm using something as follows
def search(self, x) -> BSTNode:
pass
I know I can use strings as type hints, but I'm not sure it's the correct or best way of doing it.
def search(self, x) -> "BSTNode | None":
pass
In Python prior to 3.10, the pipe doesn't have any special meaning in string-typed type hints. In Python ≤ 3.9, I'd thus stick to the ways PEP 0484 documents for union types:
Either
from typing import Union
def search(self, x) -> Union[BSTNode, None]:
pass
or, more concisely but equivalent
from typing import Optional
def search(self, x) -> Optional[BSTNode]:
pass
In Python ≥ 3.10, you can indeed use | to build representations of a union of types, even without the quotation marks. See Jean Monet's answer, PEP 604 -- Allow writing union types as X | Y, What’s New In Python 3.10 or the Python 3.10 documentation of the new types.Union type for details.
Thanks to PEP 604 it is possible to use the pipe | notation, equivalent to Union:
def func() -> int | None:
...
my_var: int | None = None
# same as
from typing import Optional, Union
def func() -> Optional[int]:
...
def func() -> Union[int, None]:
...
my_var: Optional[int] = None
my_var: Union[int, None] = None
The feature is introduced in Python 3.10, but you can start using it in Python 3.8 and 3.9 (not sure for 3.7?) by importing annotations at the beginning of the file:
from __future__ import annotations