typing.Cast as an annotation alternative to typing.cast

typing.cast is very helpful and I find myself using it quite a lot when trying to be type-safe (especially when converting old code).
However, it always bothered me that the only way to cast types is during runtime by calling an empty function which hurts performance for the sake of type-checkers that do everything else statically using annotations.

My suggestion is to add a way to cast types using annotations.
Instead of:

from typing import cast

a = cast(str, b)

We could do:

from typing import Cast
a: Cast[str] = b

While the name Cast could be improved, I think the concept of type casting using annotations would be quite intuitive since every other typing functionality is done using annotations (or some sort of a decorator which has runtime cost only for the initial setup)

1 Like

Your proposed alternative wouldn’t replace all use cases for cast, because it’s frequently used deep inside an expression, not in an assignment. So we’d have to maintain two ways to do the same thing.

It’s going to be rare that a cast() call is really a performance bottleneck: calling a simple Python function is pretty fast in 3.11 and if we believe Mark Shannon it will be even faster in the future. However, if you’re really worried about the performance cost, you can use # type: ignore instead of a cast.

1 Like

As I was writing that post I came to a similar conclusion. Having 2 ways to cast types will go against the zen of python. I also had some thoughts but unfortunately they are not too organized in my head right now but I feel I need to lay them down in no particular order.

  1. Just to be sure. While this means supporting 2 ways of casting types, you don’t duplicate any code. The casting function will be implemented by using the Cast type so type checkers would need to add support to just one way of doing it.

  2. While the runtime cost is minimal. The fact that it’s being used for static type checking (which theoretically could be zero cost) is heart breaking. It’s not anything that is critical by any means, but it really feels like there should be an easy solution somewhere.

  3. Perhaps generalizing the problem would be a worth while solution where the development cost would be higher, but the value would also be useful in other situations as well.

    a. One way could be to find a way to use annotations in expressions. Something like:

    from typing import Cast
    range(foo as Cast[int])

    However, this would include a change to the syntax which will programmers will need to learn
    and even though other languages (such as typescript) use a similar syntax a big change to the
    language that all developers would need to know about for the sake of a feature that has such a
    small value in comparison.

    b. Your quote of Mark Shannon may suggest a different approach. Maybe we can take all of these
    “empty functions” that exist merely for the sake of “marking” an object, and have an optimization
    that applies to all of them. Admittedly I’m not familiar too much with the underlying C-API but
    perhaps a special object that’s optimized for these specific cases? Maybe specializing the newly
    adaptive interpreter to handle such cases by skipping the CALL instruction (or some kind of a
    special instruction specific for these kind of objects?).

    I have no idea how hard/easy it is to implement such changes but if it all happens behind the
    scenes and the programmer wouldn’t know about them it would be easier to maintain and without
    worrying about backwards compatibility.

Even if none of these “solutions” are applicable here I still would love to hear your thoughts about them to learn about the approach taken when writing the language.

Cannot the optimization go further so that cast() will not cause any function call at runtime? Like if cast(TypeX, expression) was replaced by (expression).

Note: Should side-effects of evaluating TypeX be guaranteed? I hope the should not :slight_smile:

From my (very-very-limited) understanding it would be a bit more complicated than that. cast can be any arbitrary variable since only at run time is it evaluated to be the function “typing.cast”. If you wanted the optimizations to be on the interpreter side it can be really complicated to “know” that “typing.cast” is the actual function being called. Also, if the optimization is specific for this one function it will be harder to maintain and will most likely spaghettify the code.

This is why in my reply, I tried to have some solution that’s more generic and doesn’t revolve only around “cast” itself.

However, as someone who doesn’t understand Python’s low-level code, I might speaking out of my rear right now.

As for your side-note, it’s really weird if someone relies on side effects for stuff like annotations. It would also be weird to rely on side-effects for something that basically exists exclusively for type-checkers. Should it be guaranteed because of that? I’ll join you to that “I hope not” sentiment.