Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

Proposal: Add coerced type narrowing similar to 'cast'

未關閉
#1,773 2 則留言 4 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
5/5
預估耗時
一週以上
新手友好度
25/100
Issue 類型
功能
描述清晰度
需要釐清
活躍度
停滯
技術堆疊
python
領域
devtools

研究方向

該 issue 未列出任何檔案、測試或實作入口。首先檢視提議的 type_assert 和 ensure_type 形式,以及與 Intersection 和 Not 相關的討論;在實作之前定義支援的語意、執行期行為和驗收測試。

由索引模型根據 Issue 內容生成。

描述

topic: feature

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)?

  1. cast completely 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"
    
  2. cast is 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 exist
    
    def 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
    
  3. cast can't do Intersection (yet). explained below.

Why type_assert and not a normal assert?

  1. assert makes it slower at runtime, and sometimes we care
  2. assert can break things when improving types of an old code base

The downsides:

  1. Without assert or if-else conditions at runtime, the coerced narrowing ignores the real type and is dangerous, almost like cast.

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_assert we probably can stop adding @runtime_checkable to Protocols, if we only added it for this kind of type-hint only issues that assert isinstance(animal, CanFlyProtocol) solved.
Performing isinstance with 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:

  1. Add a similar ensure_not_type

    def function(arg: Union[int, str, list]):
        ensure_not_type(arg, int)
        reveal_type(arg)  # Revealed type: "str | list"
    
  2. Wait for the Not[] type

    def function(arg: Union[int, str, list]):
        ensure_type(arg, Not[int])
        reveal_type(arg)  # Revealed type: "str | list"
    
主要語言
Python
星號
1.8k
分支
302
平均合併
4 小時 38 分鐘
30 天內合併 PR
6

環境準備

這個專案沒有提供開發容器、Dockerfile 或貢獻指南,環境需要你自己搭建:先看它的 README,通用步驟見我們的新手貢獻指南。

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

python/typing 的其他 Issue

查看 python/typing 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。