In Python, everything is treated as an object. This means that CPython interpreter will decide on the fly, what is the type of each variables or the function return type depending on the current state. This results in significant performance hits when compared to languages like C++ where the variables and functions are declared with a type explicitly. However, we also have an option to define type hints for the variables and functions in Python. However this is gladly ignored by the interpreter, and is only useful during linting. My question is can't the type hints be used to infer the types, and making the execution faster?
Type hinting is a relatively recent addition to Python, so older code, and still most code, doesn't use them at all. Annotated code still needs to work with that existing code, so Python is a gradual system where typed and untyped code mixes together in one program.
When they are used, for most types they are purely structural, saying that certain methods are available but not where they came from, but for some builtins at least (like
int) they do convey concrete implementation aspects that could be optimised. There are cases where static type information could usefully help in optimising code execution, but perhaps not as many as you'd expect.
Relying on the annotations would also require that they be correct, which Python makes no guarantee of. Ensuring this correctness needs sound gradual typing, which requires run-time checking of types as well and carries a whole lot of run-time and semantic effects. It would be a really significant change to Python semantics to start enforcing these type annotations in that way, so the informational content of an annotation is even a little bit lower than it looks. In any case, the costs of the dynamic checking to make it sound enough to rely on almost certainly outweigh the performance benefits in almost all cases.
For a language like Python where code making use of very dynamic features and metaprogramming is fairly common, the benefit for most programs likely isn't high enough to be worth the costs no matter what. Even the long-standing nominal typing elements in Python (like
collections.abc.Sequence) doesn't convey much about the actual implementation, and mutating classes at run time is not uncommon inside common Python frameworks. These objects need to be usable with all the type-hinted code, so that code needs to deal with a big variety of object shapes. This is all very different to what's typical in C++, where these things might happen at (or before) compile time, if anything.
Instead, just-in-time compilation is a more realistic performance enhancement to Python, which is also not included in the CPython implementation, but is in some alternative versions. For high-performance Python code, as used in some scientific computation, there are both libraries like numpy that keep everything inside, and specialised JIT systems like Numba that optimise specified functions at run time. These are more practical performance targets for Python.
Python's typing model is intended for outside checkers to check python code. Pretty much any expression can go in a type hint:
x: print("hi") = 1
Obviously any typechecker will reject that as bogus, but the Python interpreter has to support it in order to fulfil the Python standard.
Leading on from that, there's no guarantee that the type hints are going to be correct, because by the Python standard, they don't have to be. This is also perfectly valid to an interpreter:
x: str = 1
As for why the Python standard has been designed this way, it's a very long story that I don't know enough of to recount here, but it comes down to "it doesn't fit Python's ideals as a language".
In terms of using types to speed something up, check out
mypyc - it uses the fact that the code has to be correctly checked by
mypy before it compiles code in order to speed up typed python code significantly.
For example, this would be sped up in exactly the manner you've described by
mypyc, but it can't be by a normal Python interpreter:
def fib(n: int) -> int:
if n <= 1:
return fib(n - 1) + fib(n - 2)
Python is a dynamically typed language, and neither its developers nor most of its users want it to become a statically typed language instead. It's true that there are possible positions between "type annotations are ignored by the interpreter" and "all expressions have a statically-known type". But any point along that spectrum other than "type annotations are totally ignored" requires the language itself to have some kind of static type system, in order for those type annotations to have any meaning.
So Python ignores type hints because the Python language isn't supposed to have a static type system at all. This is very reasonable for a language which started without having type annotations at all; there are many decisions to make when designing a static type system, but unlike designing for a new language, there are already users who would be upset about a type system that doesn't work the way they would want it to.
For example, a new language can decide to have nominal types or structural types, and users who don't like the choice can just not use that language; but if you take an existing dynamically-typed language and give it a structural static type system, the users who would want nominal types instead will be upset.
The natural solution is to leave the design of type systems to third parties. If one group of users want nominal types, and another wants structural types, they can just use different type checkers and everyone is happy. If the Python language itself privileged one of those type systems by having types affect runtime behaviour, then not everyone would be happy.
Another consideration is the release schedule. Think about Typescript, which likewise has optional type annotations which aren't checked at runtime and don't affect the behaviour of code at runtime; Typescript is continually developed, and puts out minor versions more often than Python. However, Typescript doesn't follow the convention of "no breaking changes in minor versions", because too many improvements to Typescript are expected to be breaking changes. Every time the type-checking algorithm is improved, that means that some mistakes will be caught which weren't before, and so some existing code which previously compiled without errors will now have errors.
On the other hand, Python mostly does avoid breaking changes in minor versions, so if the Python language included rules for checking type annotations, we'd probably be on Python 12 by now. Or (more realistically) the type system would have to be much more stable, with improvements made much more rarely than users want to receive them. This weighs in favour of the Python language and Python typecheckers being separate, third-party projects with their own release schedules.
In both the design of the Python language, and the design of the CPython interpreter (effectively the default implementation of Python, but there are others), performance is not a major objective.
Python has a relatively simple language design that comes with a lot of openness: programs can inspect and modify objects at runtime. A lot of libraries take advantage of that. This precludes a lot of optimization unless you know that no library is doing any patching of some core classes.
Type annotations don't really help with that because there's no requirement for them to be sound. Your program is still supposed to run as written if the type annotations are wrong. (You'd surely prefer it to run as intended — but for that you need a language that's designed as type-safe from the start. That wouldn't be Python.)
CPython does not even try to perform much optimization because it values debuggability over performance. In Python, you should be able to trace the execution of a program line by line. That doesn't work with an optimizer that reorders code around.
If you want performance in Python, you write the performance-sensitive parts in a low-level language like Fortran or C, and the high-level parts in Python. For example, Python is commonly used for numerical computation (a performance-demanding field) with numpy (not written in Python) providing a high-performance implementation of basic operation, and Python used to combine the high-performance building blocks.