Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add safer types of casts #565

Open
gvanrossum opened this issue Jun 19, 2018 · 6 comments
Open

Add safer types of casts #565

gvanrossum opened this issue Jun 19, 2018 · 6 comments
Labels
topic: feature Discussions about new features for Python's type annotations

Comments

@gvanrossum
Copy link
Member

The only form of cast expression we have is intended to completely bypass the type system. This is like most forms of casting in C (not counting conversions) -- it never fails either at runtime or at compile time.

Maybe we can add two new types of casts:

Downcast

downcast(T, E) checks at compile time that the type of expression E is a supertype of T, and checks at runtime that E is in fact an instance of T. As a special case, if the type of E is Any, the compile time check always succeeds, but the runtime check is still performed.

A possible implementation:

def downcast(type, expr):
    assert isinstance(expr, type)
    return expr

It's intentional that this uses assert (though debatable): the intended use case is currently handled by inserting the same assert manually. IOW:

x = downcast(type, expr)

is roughly equivalent to:

assert isinstance(expr, type)
x = expr

Upcast

Probably much less needed, but proposed for symmetry and because occasionally it's useful. upcast(T, E) should check at compile time that T is a supertype of the type of E, and at runtime it's a no-op.

A possible implementation:

def upcast(type, expr):
    return expr

This fragment:

x = upcast(type, expr)

is roughly equivalent to:

x: type = expr

except that it works even if the type of x has already been declared.

@gvanrossum
Copy link
Member Author

FWIW I'm not sure what the spec should say when T is generic.

@ilevkivskyi
Copy link
Member

and checks at runtime that E is in fact an instance of T

This will work only for simple classes. TBH I don't see a big win over the current way (using isinstance()). Practically everyone who uses Python knows what assert isinstance(obj, Cls) does, but the downcast form that just saves one line can introduce some confusions (especially for people who can't remember up/down).

@gvanrossum
Copy link
Member Author

TBH I'm not feeling strongly myself, but I've definitely found code where people needed an upcast and used cast(), bypassing the type safety offered by mypy.

@JukkaL
Copy link
Contributor

JukkaL commented Jun 20, 2018

This could be somewhat useful feature, especially for retrofitting types to a legacy codebase. However, supporting anything beyond simple classes would be hard (see below).

TBH I don't see a big win over the current way (using isinstance()).

Sometimes the need for a cast is deep within an expression, and using an assert would require somewhat non-trivial refactoring. People will often use a cast instead of isinstance() in cases like that, and downcast(...) would be a reasonable thing to have. I think that isinstance() would be preferable when writing new code, though.

Some discussion of special cases:

  • What if the target type is Any? For consistency, this should perhaps be allowed, for both up and down casts (changing anything to Any should not generate additional errors).
  • Casting from List[int] to List[object], and vice versa, would be disallowed. For these an unsafe cast would be needed, which is fine, since the cast is unsafe.
  • Callable types, tuple types, type object types and generic types could be used as upcast targets, but they wouldn't work with the isinstance() in downcast. This would be inconsistent.

One way to work around some of the limitations would be to remove the isinstance() check from downcast. This way all the functions would be more uniform -- they would have the same runtime effect (none), and the only difference would be the supported source/target type combinations. Maybe for each valid upcast from type A to type B the corresponding downcast B to A could be allowed?

@ilevkivskyi
Copy link
Member

This way all the functions would be more uniform -- they would have the same runtime effect (none), and the only difference would be the supported source/target type combinations.

The asymmetry in the original proposal also seems a bit inconsistent to me. I would expect downcast/upcast to behave the same way as cast plus an is_subtype check in mypy.

@gvanrossum
Copy link
Member Author

To defend the asymmetry: in other languages downcasts always cause a runtime check to be generated, while upcasts don't. Possibly we could have different names. (C++ has static_cast<T>(E) and dynamic_cast<T>(E) but I can't tell exactly how they differ from the reference I found -- I think a common case is for the dynamic cast to be used as a downcast, and it definitely generates a runtime check.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: feature Discussions about new features for Python's type annotations
Projects
None yet
Development

No branches or pull requests

4 participants