# Add a safer version of cast?

Consider this code:

``````    def f(interest: float) -> float:
return sum(ci * interest ** (i + percent_into_year)
for i, ci in enumerate(reversed(contribution_limits)))
``````

Unfortunately, type checkers think that `float ** float` is `Any`. (See the typeshed for why.)

MyPy warns me:

``````error: Returning Any from function declared to return "float"  [no-any-return]
return sum(ci * interest ** (i + percent_into_year)
``````

To solve the problem, I could:

• Add a `type: ignore`, or
• cast the expression to float.

What about a safer version of cast:

``````    def f(interest: float) -> float:
return sum(ci * safe_cast(float, interest ** (i + percent_into_year))
for i, ci in enumerate(reversed(contribution_limits)))
``````

Or perhaps instead of `as_instance(x, T)` or `cast(T, x, verify=True)` (which would only work for certain non-generic types?)

Either way, this would verify that `isinstance(x, T)`, and then return x. Itâ€™s like a safer version of cast.

Can you cast the expression to `float`, and also add:

``````assert percent_into_year >= 0
``````

Yes, but that still has the problem that youâ€™ve moved some of the verification onto yourself.

Also, I think this is a more general problem

Doesnâ€™t `assert isinstance(x, T)` work? I thought type checkers could infer from such an assertion that the type of x is now T.

I tried to test this, and neither mypy nor pyright reported the error you describe. I used both your code (which uses a couple of undefined variables, so I guessed) and

``````def f(x: float, y: float) -> float:
return x ** y
``````

Can you provide a reproducible example of getting this error?

1 Like

Why not just define it yourself[1]?

``````def as_instance[T](x: object, type: type[T]) -> T:
assert isinstance(x, type)
return x
``````

But as others have pointed out in most cases you can just do the assertion inline, itâ€™s mostly comprehensions where you may want to cast a subexpression.

1. since you only seem to care about cases where `isinstance` works this should be powerful enough â†©ď¸Ž

I tried to test this, and neither mypy nor pyright reported the error you describe.

You need to enable â€śwarn_return_any = trueâ€ť for MyPy to warn on this. Anyway, even if you have this off, having this be untyped may be undesirable for you (if, for example, you want other things to be checked).

Yes, you could do:

``````def safe_power(x: float, y: float) -> float:
retval = x ** y
assert isinstance(retval, float)
return retval
``````

and then use it in place of the original power in `f`. Is that your preferred solution?

Right, you can absolutely build this yourself. My proposal is to add this to typing. By the way, I would prefer the second argument to a type-form rather than a type to ensure that you can pass in unions, etc.

I think my issue is that I canâ€™t do it inline. I need to break out a special function.

Itâ€™s certainly perfectly adequate IMO. Or write your own `as_instance` as @Daverball suggested. I definitely wouldnâ€™t bother looking for a stdlib function to do this.

1 Like

I donâ€™t think mypyâ€™s `no-return-any` is serving you (or other users) well here. The coercion to `float` (type system level coercion) happens due to your return annotation. You donâ€™t have a better type from the standard library available here due to limitations in how the numerics were and are currently handled, and mypyâ€™s no-return-any is breaking the gradual guarantee that you can replace any correct annotation with Any and not receive new errors.

While Iâ€™m sympathetic to fixing the numeric situation and reducing the number of places the standard library needs to return Any, I donâ€™t think what you have here is a good argument for a different cast behavior. cast is always exactly as safe as your ability to ensure the correct invariants are upheld.

The choice to type this as Any rather than float | complex may hide false positives, but it also hides actual possible bugs, If we pretend for a moment that this was typed as float | complex, casting if you know this is always a positive power, or checking to ensure if it isnâ€™t would be correct here if this were typed accurately. If youâ€™re reintroducing a need to check by breaking the gradual guarantee, why would the same checks you would be otherwise required to do be inadequate here?

Fair enough, Iâ€™ll use one of those solutions for now. This problem has come up for me at least a few times. I thought I would propose the idea to see if other people have run into it too.

We could add this function to GitHub - hauntsaninja/useful_types: Useful types for Python.

1 Like

Iâ€™m not sure if safer is the right word, but Iâ€™d like a `check:bool=False` option for `typing.cast` to do a runtime check for the type of the instance.

Iâ€™ve had cases where people use `cast(..)` to get mypy to agree (incorrectly) when that assert on type would have caught the wrong type going in.

I donâ€™t usually care for asserts in production code, but raising something, optionally, during the cast would be nice.

Having a default of false keeps behavior the same for all current users.

Yes, but that still has the problem that youâ€™ve moved some of the verification onto yourself

So what? Static typing doesnâ€™t remove the programmerâ€™s obligation to sanitise untrusted inputs.

Without that assert or something else, Mypy canâ€™t otherwise assume that `percent_into_year` is not negative

The typeshed comment is a little incorrect. The thing weâ€™d be concerned about being negative is `interest` here. And interestingly, negative interest rates are a real thing, so itâ€™s not actually correct to just assume this without specific additional information. Handling negative interest rates using `complex` would also be incorrect to how these function in the real world. Hence, itâ€™s a bit of a lose-lose here that python automatically treats powers that would result in a complex as a complex without user opt-in. Youâ€™d arrive at the wrong result, and the type checker still couldnâ€™t warn you of it because it was deemed too noisy to type this correctly in the type shed.

My apologies. Youâ€™re quite right. I shouldâ€™ve been asserting `interest >= 0`.

@NeilGirdhar I can actually see safe_cast or strict_cast would be quite useful in other circumstances. It would allow telling the typechecker what you want it to check explicitly.

But in cases such as in your example, where the bug is due to specific values of an argument (e.g. negative ones) not its general type, then I think the code is more readable and easier to understand if that case (negative values) is explicitly tested for.

The proposed function is an assertion.

And even with your assertion, MyPy wonâ€™t be able to figure out that the return type is `float`.

If you add the assertion on the value, and then add a `cast` or `type: ignore` to stifle the error, you will still need to add a comment explaining why the cast is okay. Something like:

``````# Ensure that the result of power is float so that we can ignore the type error.
assert percent_into_year >= 0
``````

Even with the comment, itâ€™s not great since it still relies on ensuring that the assertion matches the code and the type error can really be suppressed.

Whereas with `safe_cast(float, ...)`, you are ensuring what you want (that the type is float) directly, and you donâ€™t need a comment. In general, good code reflects intentions. This makes it self-commenting.

On top of that, if the code changes, the assertion keeps getting run and if something goes wrong, it should catch. It does not require vigilance. In general, itâ€™s better to do instance checks than to suppress type errors.

I disagree. I find an inequality assertion before a mathematical calculation, both using elementary Python syntax to be not only sufficient but self explanatory.

Furthermore, the existing basis, and your idea are called `cast` and `safe_cast` respectively, but they are not true type casts. Python is still a dynamic language. If your intention is to have a true type cast, then why not just call `float` on it?

Iâ€™m fortunate that I have experience of lots of different programming languages, and know what type casting is. But if I didnâ€™t know that (like the majoriiy of Python users), Iâ€™d far prefer the unambiguous maths, to having to search the docs to find the implementation details about whatever the latest sparkling new feature of the type system is. Especially if its name misleads the reader about what it actually is.

Asserting values is not sufficient to justify the `cast` that you need on the next line. So you either need a comment or an instance check. If the code were imperative, you would have a regular instance-check. This is the comprehension version of that.

The intention isnâ€™t to do a type cast. The intention is to suppress the type error safely.

I suspect `typing.cast` was so-named because it type casts the type form that is associated with an expression.

The intention isnâ€™t to do a type cast. The intention is to suppress the type error safely.

In that case `cast` and `safe_cast` are bad names for their features.

What are you using `float`s for financial calculations for anyway? By definition, `float`s are prone to Floating point error. Isnâ€™t best practise to use an `int` of the smallest denomination (e.g. pennies) or a Decimal type to some fixed precision?

You shouldnâ€™t assert on the value, you should assert the type. Youâ€™re saying â€śI am sure this will be a floatâ€ť, which is what an assertion is. You can add a comment by the assert saying â€śthis is safe because `percent_into_year >= 0`â€ť. Iâ€™m not even clear why you think `assert isinstance(..., float)` is any less clear than `safe_cast(float, ...)` - the two seem essentially identical to me.

If you want to check the value then you should either do a proper runtime check, with an actual error (not an assertion failure), or you should include an additional assert, whose purpose is to ensure that the caller is adhering to the API contract. That assert is independent of the type check, except in that it validates the conditions needed to ensure the type assert is safe.

If none of the above match your views on how to handle this situation, thatâ€™s fine. You can write your own `safe_cast`, as weâ€™ve seen. But with so many existing ways of handling this situation, adding yet another approach to the standard library seems unnecessary.

Iâ€™m glad you agree with me! Asserting on the type is exactly what I suggested as being superior than asserting on the value:

Where do you get the impression I said that one is less clear than the other? This is what I said:

I never proposed checking the value. Although I have to disagree with you about raising errors in cases where youâ€™re certain that something wonâ€™t happen. I think assertions are the correct approach for that case.

The point of `safe_cast` is to have an inline version of `isinstance` since your solution would require defining a new function.