I like adding | as an alias for Union, and the PEP has lots of examples of where typing.Union gets gnarly really quick, but that doesn't seem like a great example in the changelog:
def square(number: int | float) -> int | float:
return number ** 2
A TypeVar constrained to int and float, T = TypeVar('T', int, float), should be used instead to indicate that the return type is the same as the argument type, no?
This example demonstrates why I don't understand Python's type annotations. They seem to be incompatible with the language's core principle of duck typing.
Why would you constrain a function like `square` to only accept `int`s and `float`s? What if I want to square, say, a `fractions.Fraction`?
Interface typing > duck typing. With any sort of code completion, it's 1000x easier and more productive to figure out what goes where when the computer has some idea of the types of a given varaible, and hence what operations you can do to it.
It's like duck typing with a built-in Audubon society taxonomy chart.
You can always do whatever you want at runtime, it's still python.
IMHO, duck typing is strictly inferior to interface typing. You'd define a `Number` interface which `int`, `float`, ect all implement.
Duck typing has been fundamental to Python for a long time. Do you think we're seeing a shift away from it? In the future, do you think "Pythonic" code will include significant use of explicit interfaces?
> Duck typing has been fundamental to Python for a long time. Do you think we're seeing a shift away from it?
We have been since at least the introduction of abstract base classes; even in purely dynamic python, having the ability to more explicitly declare and interrogate intent than pure duck typing is frequently useful.
> do you think "Pythonic" code will include significant use of explicit interfaces?
Sure, as it already does via abcs. But it will also still use lots of duck typing, though over time more of it will be “static duck typing” by way of protocols.
Are "protocols" the python term for what Go (and I guess java but I'm way rusty at java) calls "interfaces"? eg the set of methods exposed by an object.
>>> import numpy as np
>>> np.array("asdgh")**2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ufunc 'square' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
There is a numbers.Number for that - but yeah, that's another way in which it's just not a good example..
I think typing_extensions.Protocol helps support the general duck typing "any object that has method x", though there are some implementation issues w/ dataclasses and such..
That won't help if I want to square a matrix, or a polynomial.
> typing_extensions.Protocol helps support the general duck typing "any object that has method x"
Even if there's a `powerable` protocol that could be used to annotate `square` and `cube`, how would you annotate a slightly more complex function like `poly`?
> how is a user to find out whether square really accepts a matrix?
How do you find out that a function exists, what it accepts, and what it does normally?
> From comments? They are again limited by the authors imagination.
Sure, but an author which much experience with duck typing will typically write documentation that specified that expected methods (informal structural “types”) rather than nominal types.
With the static equivalent (protocols), it’s conceivable that this is also discoverable via tooling.
Using maths to either oppose or support generic duck typing is just pointless, because it's easy to come up with counterexamples where a mathematical operation would or would not behave as expected.
In quite a few cases where one would overload a mathematical operation to, say, sum two objects of certain type, it'd be better to explicitly type out the operation as a functor/lambda.
Maybe something like `numpy.typing.ArrayLike|numbers.Number`. Not incredibly precise. But if this covers also pandas.Series (not entirely sure), then I think it would cover 99.9% of my practical needs.
numpy arrays are effectively the standard library datatype for matrices in Python land.
Type annotations are just hints and aren't enforced at runtime. You can pass whatever you want into that function, and you can also create a union type with the Fraction if you want the annotations to match your application.
Type hints don't help performance either because the interpreter discards them. The hints are not enforced by CPython, so you now need a build tool instead of just running the script. The whole point of PEP 484 seems to be to enable autocomplete in Visual Studio Code.
> The whole point of PEP 484 seems to be to enable autocomplete in Visual Studio Code.
It's to enable tooling, but not just autocomplete. The main purpose of typing is preventing type-related bugs, though making editor autocompletion and other editor information more robust is a not-insignificant additional benefit.
PEP 484 (created 29-Sep-2014, accepted 22-May-2015) wasn't motivated by VS Code (announced April 29, 2015).
PyCharm understands type hints for years now. Without using mypy btw. VSCode should be able to that, there should be one or more (either finished or half baked) plugins for it.
Edit: Microsoft Python Language Server has it (i read)
It has nothing to do with any particular editor, it is for autocomplete and build time type checking in any editor and type checking in people's build chains.
Your code should get type checked whenever you run tests and can help reveal subtle bugs (for example, allowing Optional but not handling None in all code paths)
The word you are looking for is structural typing. (as opposed to nominal typing)
It's essentially type-safe duck typing. In the case of square, you say i want x and y to support the multiply-operator, but i don't care what type they are otherwise.
I was just thinking that squaring an imaginary number results in a real number - that's a pretty crazy thing for a type system to handle, no? I guess that means the correct annotations would be more along the lines of:
def square(number: numbers.Complex) -> numbers.Real: ...
T = typing.TypeVar('T', numbers.Real)
def square(number: T) -> T:
return number ** 2