i : int | list[int] = 0
def foo() -> None:
global i
i = []
def bar() -> int:
if isinstance(i, int):
foo()
return i
return 0
print(type(bar()))Don't do what?
- Don't write unsound code? There's no way to know until you run the program and find out your `int` is actually a `list`.
- Don't assume type annotations are correct? Then what's the point of all the extra code to appease the type checker if it doesn't provide any guarantees?
You may as well argue that unit tests are pointless because you could cheat by making the implementations return just the hardcoded values from the test cases.
class C:
def __init__(self) -> None:
self.i : int | list[int] = 0
def foo(self) -> None:
self.i = []
def bar(self) -> int:
if isinstance(self.i, int):
self.foo()
return self.i
return 0
print(type(C().bar())) class C {
i: number | number[];
constructor() {
this.i = 0;
}
foo(): void {
this.i = [];
}
bar(): number {
if (typeof this.i === 'number') {
this.foo();
return this.i;
}
return 0;
}
}
console.log(typeof new C().bar());
It seems to be a problem with "naked" type unions in general. It's unfortunate.If you want to be able to change the type of something at runtime, static analysis isn't always going to be able to have your back 100% of the time. Turns out that's a tradeoff that many are willing to make.
[1] Unfortunately, many important 3rd party libraries aren't typed. I try to wrap them in type-safe modules or localise their use, but if your codebase is deeply dependent on them, this isn't always feasible.