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

Do we need an explicit cast() function? #15

Closed
gvanrossum opened this issue Oct 16, 2014 · 13 comments
Closed

Do we need an explicit cast() function? #15

gvanrossum opened this issue Oct 16, 2014 · 13 comments

Comments

@gvanrossum
Copy link
Member

In mypy there's a cast(TYPE, EXPR) function. Do we still need this, given that we can do the same thing with # type: comments? (However, see issue #9.)

@JukkaL
Copy link
Contributor

JukkaL commented Oct 16, 2014

Mypy treats # type: comments differently from casts. When using a comment, types still have to be compatible. For example, this is not valid:

def f(x: Sequence[int]) -> None:
    y = x # type: List[int]   # Error, since Sequence is not compatible with List

However, this is okay:

def f(x: Sequence[int]) -> None:
    y = cast(List[int], x)   # Fine

Effectively, type annotations (comments) are type safe (or as type safe as anything in mypy), but casts can do arbitrary things and work around the restrictions of the type system.

Also, cast can be used within expressions, whereas a type comment is only valid for assignment statements. This is often useful. Here is a toy example:

foo(cast(List[int], x))

If we used a type annotation as a cast, this would require an extra local variable:

tmp = x # type: List[int]
foo(tmp)

In more complex cases the refactoring could be non-trivial (it could break the order of evaluation in a boolean expression, for example).

@ambv
Copy link
Contributor

ambv commented Jan 7, 2015

@JukkaL, do you have non-toy examples where working around the type system with cast() is necessary? That would be good to put in the PEP.

@JukkaL
Copy link
Contributor

JukkaL commented Jan 8, 2015

It's actually a bit tricky to give a short, realistic example where a cast is clearly useful. Here is one inspired by a similar example in mypy:

def find_first_str(a: Sequence[object]) -> str:
    index = next((i for i, s in enumerate(a) if isinstance(s, str)), -1)
    if index >= 0:
        return cast(str, a[index])
    else:
        raise ValueError

Every example with a cast can be rewritten to not need a cast, but when adapting existing dynamically typed code, introducing a cast is often the simplest way of getting a piece of code to type check.

@gvanrossum
Copy link
Member Author

It's good enough for me. :-)

I also like using cast() to initialize result variable with an empty container of the right type:

def word_count(input: Iterable[str]) -> Dict[str, int]:
    res = cast(Dict[str, int], {})
    for w in input:
        res[w] = res.get(w, 0) + 1
    return res

@JukkaL
Copy link
Contributor

JukkaL commented Jan 8, 2015

Yeah, cast() can be used with empty containers, but it has the drawback that you don't get an error from mypy if you give a bogus type:

a = cast(Dict[str, int], [])   # ouch, no error

If you use a # type: comment, you'll get an error:

a = []  # type: Dict[str, int]   # error, incompatible types in assignment

cast() just blindly accepts whatever you give it. This is different from Java, for example, where the compiler catches some bogus casts. Mypy could also do that, but only for built-in types (since they don't support arbitrary multiple inheritance).

@gvanrossum
Copy link
Member Author

Maybe we need different kinds of casts, like in C++? This makes me a little
sad, so much complexity to satisfy the type checker in rare cases.

On Wednesday, January 7, 2015, Jukka Lehtosalo notifications@github.com
wrote:

Yeah, cast() can be used with empty containers, but it has the drawback
that you don't get an error from mypy if you give a bogus type:

a = cast(Dict[str, int], []) # ouch, no error

If you use a # type: comment, you'll get an error:

a = [] # type: Dict[str, int] # error, incompatible types in assignment

cast() just blindly accepts whatever you give it. This is different from
Java, for example, where the compiler catches some bogus casts. Mypy could
also do that, but only for built-in types (since they don't support
arbitrary multiple inheritance).


Reply to this email directly or view it on GitHub
#15 (comment).

--Guido van Rossum (on iPad)

@ambv
Copy link
Contributor

ambv commented Jan 8, 2015

How about:

cast(Dict[str, int], [])   # error
with typing.skip_type_checks:
    cast(Dict[str, int], [])   # okay

@gvanrossum
Copy link
Member Author

Łukasz, I presume you're talking about errors from the static checker only. After Jukka's comments in #33 I think it's better to use comments to disable the type checker than with-statements.

Doing runtime checks in cast() is madness anyway (it took me a little while to realize this, but there are too many ways it can fail incorrectly, modify the value by even looking at it, or waste a lot of time).

@ambv
Copy link
Contributor

ambv commented Jan 14, 2015

Yes, those would be static checker errors only.

Fixed in e2e6fc4 (advertises usage of @no_type_checks for routines and "# type: ignore" for lines).

@ambv ambv closed this as completed Jan 14, 2015
@ambv
Copy link
Contributor

ambv commented Jan 14, 2015

Oh, the core issue, whether we need cast() is still open. @JukkaL, @gvanrossum, now that we endorse "# type: " comments, maybe a specific comment would be better suited for this?

Jukka, you were worried about runtime performance of cast(), the comment would resolve that.

@ambv ambv reopened this Jan 14, 2015
@JukkaL
Copy link
Contributor

JukkaL commented Jan 15, 2015

As mentioned earlier, a # type: comment is not a replacement for a cast, since # type: comments only act as hints and the type checker will still verify that the initializer type is compatible, whereas the type checker will assume that casts are valid and may allow code that does bad things at runtime. This is similar to Java variable type definitions vs. casts -- only the latter can be used to write 'unsafe' code.

So this would be rejected by the type checker:

x = <expression> # type: object
y = x # type: int  # error, since object is not compatible with int

But this would be okay:

x = <expression> # type: object
y = cast(int, x)  # okay (casts are usually used to go from a general to a narrower type)

Cast should be an expression, since casts are often used within subexpressions. # type: comments, on the other hand, are only used for declaring the types of variables, so they are always applied to an assignment statement, and thus there is no need to nest them within expressions.

@gvanrossum
Copy link
Member Author

Decision: we'll add cast() to the PEP. Closing this issue; the TODO is added to the README.rst.

@refi64
Copy link

refi64 commented Nov 30, 2019 via email

hauntsaninja pushed a commit to python/mypy that referenced this issue Sep 4, 2021
1. Changed an outdated example to an original one from python/typing#15 (comment)
2. I've listed more type narrowing techniques, like `type(obj) is some_class`
3. Fixed several headers

Co-authored-by: Terence Honles <terence@honles.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants