Proposal: Add coerced type narrowing similar to 'cast'
还没有人认领这个 Issue。
- 主要语言
- Python
- 星标
- 1.8k
- 派生
- 302
- 平均合并
- 23 小时
- 30 天内合并 PR
- 8
描述
My suggestion is to add a way to coerce type narrowing without any runtime change,
by adding new type_assert and/or ensure_type.
def function(arg: Any):
type_assert isinstance(arg, int)
reveal_type(arg) # Revealed type: "builtins.int"
def function(arg: Any):
ensure_type(arg, int)
reveal_type(arg) # Revealed type: "builtins.int"
These functions do nothing at runtime! they only help with the types.
Why not cast(int, arg)?
-
castcompletely replaces the type and we need to write a full new type.
With type narrowing it's only necessary to limit the options of the existing type.def function(arg: Union[int, str, list]): arg = cast(Union[str, list], arg) reveal_type(arg) # Revealed type: "str | list"def function(arg: Union[int, str, list]): type_assert not isinstance(arg, int) # only need to remove 'int' reveal_type(arg) # Revealed type: "str | list" -
castis more dangerous because we ignore the previous type. with narrowing we just limit the options of the previous type.def function(arg: Union[int, str, list]): arg1 = cast(dict, arg) reveal_type(arg1) # Revealed type: "dict"def function(arg: Union[int, str, list]): type_assert not isinstance(arg, dict) # Error: Subclass of "int" and "dict[Any, Any]" cannot existdef function(arg: Union[int, str, list]): type_assert not isinstance(arg, int) type_assert not isinstance(arg, str) type_assert not isinstance(arg, list) reveal_type(arg) # Error: Statement is unreachable -
castcan't doIntersection(yet). explained below.
Why type_assert and not a normal assert?
assertmakes it slower at runtime, and sometimes we careassertcan break things when improving types of an old code base
The downsides:
- Without
assertor if-else conditions at runtime, the coerced narrowing ignores the real type and is dangerous, almost likecast.
With Intersection type
Until we have Intersection type, this kind of things are problematic:
class Animal:
def say_my_name(self) -> None:
print("My name is Animal")
@runtime_checkable
class CanFlyProtocol(Protocol):
def fly(self) -> None: ...
class Bird(Animal, CanFlyProtocol):
def fly(self) -> None:
print("Fly")
def let_it_fly(animal: Animal): # we can't restrict the argument type to be Animal AND CanFlyProtocol
animal.say_my_name()
animal.fly() # Error: "Animal" has no attribute "fly"
Even cast can't help us, but we can narrow the type!
def let_it_fly(animal: Animal):
assert isinstance(animal, CanFlyProtocol)
animal.say_my_name()
animal.fly()
reveal_type(animal) # Revealed type: "<subclass of "Animal" and "CanFlyProtocol">"
Type checkers can understand Intersection when we narrow the type, great!
But what if we don't want to change runtime behavior? for this we can
replace assert with the suggested type_assert or ensure_type!
def let_it_fly(animal: Animal):
type_assert isinstance(animal, CanFlyProtocol)
animal.say_my_name()
animal.fly()
reveal_type(animal) # Revealed type: "<subclass of "Animal" and "CanFlyProtocol">"
A side note: with
type_assertwe probably can stop adding@runtime_checkableto Protocols, if we only added it for this kind of type-hint only issues thatassert isinstance(animal, CanFlyProtocol)solved.
Performingisinstancewith Protocol is very slow so this benefit is not small.
ensure_type with Not
See:
def function(arg: Union[int, str, list]):
type_assert not isinstance(arg, int)
reveal_type(arg) # Revealed type: "str | list"
How to do not isinstance with ensure_type? we have two options:
-
Add a similar
ensure_not_typedef function(arg: Union[int, str, list]): ensure_not_type(arg, int) reveal_type(arg) # Revealed type: "str | list" -
Wait for the
Not[]typedef function(arg: Union[int, str, list]): ensure_type(arg, Not[int]) reveal_type(arg) # Revealed type: "str | list"
贡献指南
这个仓库没有索引到贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
该 issue 未列出任何文件、测试或实现入口。首先审查提议的 type_assert 和 ensure_type 形式以及与 Intersection 和 Not 相关的讨论;在实现之前定义支持的语义、运行时行为和验收测试。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- python
- 领域
- devtools
- Issue 类型
- 功能
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 停滞
- 描述清晰度
- 需要澄清
- 新手友好度
- 25/100