[英]What's the correct way to check if an object is a typing.Generic?
我正在嘗試編寫驗證類型提示的代碼,為了做到這一點,我必須找出注釋是什么類型的對象。 例如,考慮這個片段,它應該告訴用戶期望什么樣的值:
import typing
typ = typing.Union[int, str]
if issubclass(typ, typing.Union):
print('value type should be one of', typ.__args__)
elif issubclass(typ, typing.Generic):
print('value type should be a structure of', typ.__args__[0])
else:
print('value type should be', typ)
這應該打印“值類型應該是(int,str)之一”,但它會引發異常:
Traceback (most recent call last):
File "untitled.py", line 6, in <module>
if issubclass(typ, typing.Union):
File "C:\Python34\lib\site-packages\typing.py", line 829, in __subclasscheck__
raise TypeError("Unions cannot be used with issubclass().")
TypeError: Unions cannot be used with issubclass().
isinstance
也不起作用:
>>> isinstance(typ, typing.Union)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python34\lib\site-packages\typing.py", line 826, in __instancecheck__
raise TypeError("Unions cannot be used with isinstance().")
TypeError: Unions cannot be used with isinstance().
檢查typ
是否是typing.Generic
的正確方法是什么?
如果可能的話,我希望看到一個由文檔或 PEP 或其他資源支持的解決方案。 通過訪問未記錄的內部屬性來“工作”的“解決方案”很容易找到。 但更有可能的是,它會成為一個實現細節,並且會在未來的版本中發生變化。 我正在尋找“正確的方法”來做到這一點。
您可能正在尋找__origin__
:
# * __origin__ keeps a reference to a type that was subscripted,
# e.g., Union[T, int].__origin__ == Union;`
import typing
typ = typing.Union[int, str]
if typ.__origin__ is typing.Union:
print('value type should be one of', typ.__args__)
elif typ.__origin__ is typing.Generic:
print('value type should be a structure of', typ.__args__[0])
else:
print('value type should be', typ)
>>>value type should be one of (<class 'int'>, <class 'str'>)
我能找到的最好的提倡使用這個無證屬性的是 Guido Van Rossum 的這句令人放心的引述(日期為 2016 年):
我可以推薦的最好的方法是使用
__origin__
——如果我們要更改這個屬性,仍然必須有其他方法來訪問相同的信息,並且很容易在代碼中__origin__
的出現。 (與__origin__
相比,我不太擔心對__extra__
的更改。)您還可以查看內部函數_gorg()
和_geqv()
(顯然,這些名稱不會成為任何公共 API 的一部分,但它們的實現非常簡單且在概念上有用)。
文檔中的這一警告似乎表明尚未在大理石中設置任何內容:
如果核心開發人員認為有必要,可能會添加新功能,即使在次要版本之間,API 也可能會發生變化。
沒有官方方法可以獲取此信息。 typing
模塊仍在大量開發中,沒有公開的 API 可言。 (事實上,它可能永遠不會有。)
我們所能做的就是查看模塊的內部結構並找到最簡單的方法來獲取我們想要的信息。 而且由於該模塊仍在開發中,其內部結構將發生變化。 很多。
在 python 3.5 和 3.6 中,泛型有一個__origin__
屬性,該屬性包含對原始泛型基類的引用(即List[int].__origin__
本來是List
),但在 3.7 中改變了這一點。 現在找出某個東西是否是泛型的最簡單方法可能是檢查它的__parameters__
和__args__
屬性。
下面是一組可用於檢測泛型的函數:
import typing
if hasattr(typing, '_GenericAlias'):
# python 3.7
def _is_generic(cls):
if isinstance(cls, typing._GenericAlias):
return True
if isinstance(cls, typing._SpecialForm):
return cls not in {typing.Any}
return False
def _is_base_generic(cls):
if isinstance(cls, typing._GenericAlias):
if cls.__origin__ in {typing.Generic, typing._Protocol}:
return False
if isinstance(cls, typing._VariadicGenericAlias):
return True
return len(cls.__parameters__) > 0
if isinstance(cls, typing._SpecialForm):
return cls._name in {'ClassVar', 'Union', 'Optional'}
return False
else:
# python <3.7
if hasattr(typing, '_Union'):
# python 3.6
def _is_generic(cls):
if isinstance(cls, (typing.GenericMeta, typing._Union, typing._Optional, typing._ClassVar)):
return True
return False
def _is_base_generic(cls):
if isinstance(cls, (typing.GenericMeta, typing._Union)):
return cls.__args__ in {None, ()}
if isinstance(cls, typing._Optional):
return True
return False
else:
# python 3.5
def _is_generic(cls):
if isinstance(cls, (typing.GenericMeta, typing.UnionMeta, typing.OptionalMeta, typing.CallableMeta, typing.TupleMeta)):
return True
return False
def _is_base_generic(cls):
if isinstance(cls, typing.GenericMeta):
return all(isinstance(arg, typing.TypeVar) for arg in cls.__parameters__)
if isinstance(cls, typing.UnionMeta):
return cls.__union_params__ is None
if isinstance(cls, typing.TupleMeta):
return cls.__tuple_params__ is None
if isinstance(cls, typing.CallableMeta):
return cls.__args__ is None
if isinstance(cls, typing.OptionalMeta):
return True
return False
def is_generic(cls):
"""
Detects any kind of generic, for example `List` or `List[int]`. This includes "special" types like
Union and Tuple - anything that's subscriptable, basically.
"""
return _is_generic(cls)
def is_base_generic(cls):
"""
Detects generic base classes, for example `List` (but not `List[int]`)
"""
return _is_base_generic(cls)
def is_qualified_generic(cls):
"""
Detects generics with arguments, for example `List[int]` (but not `List`)
"""
return is_generic(cls) and not is_base_generic(cls)
所有這些函數都應該在所有 <= 3.7 的 python 版本中工作(包括任何 <3.5 使用typing
模塊后向端口的版本)。
我在我的一個項目中對此進行了編程:
def is_type(arg: object) -> bool:
"""Return True if `arg` is a type, including those for generic typing."""
from typing import Any, get_origin
generic_base = type(Any)
if (aux := generic_base.__base__) is not object:
generic_base = aux
return isinstance(arg, (type, generic_base)) or get_origin(arg)
從Python 3.8開始, typing.getOrigin(tp)
是正確的方法
文檔字符串非常具有指導意義(如果不是從typing
返回,則特別是返回 None ):
def get_origin(tp):
"""Get the unsubscripted version of a type.
This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
and Annotated. Return None for unsupported types. Examples::
get_origin(Literal[42]) is Literal
get_origin(int) is None
get_origin(ClassVar[int]) is ClassVar
get_origin(Generic) is Generic
get_origin(Generic[T]) is Generic
get_origin(Union[T, int]) is Union
get_origin(List[Tuple[T, T]][int]) == list
"""
在您的用例中,它將類似於:
import typing
def func(typ):
if typing.get_origin(typ) is typing.Union:
print('value type should be one of', typing.get_args(typ))
elif typing.get_origin(typ) is typing.Generic:
print('value type should be a structure of', typing.get_args(typ))
else:
print('value type should be', typ)
A = typing.TypeVar("A")
B = typing.TypeVar("B")
func(typing.Union[int, str])
func(typing.Generic[A, B])
func(int)
>>> "value type should be one of (<class 'int'>, <class 'str'>)"
>>> "value type should be a structure of (~A, ~B)"
>>> "value type should be <class 'int'>"
我認為,您最多可以做的就是在變量上使用您的typ
,在其上使用typing.get_type_hints
並從返回的__annotations__
類字典中提取您需要的信息。
PEP-484說:
get_type_hints()
,一個實用函數,用於從函數或方法中檢索類型提示。 給定一個函數或方法對象,它返回一個與__annotations__
格式相同的字典,但在原始函數或方法定義的上下文中將前向引用(以字符串文字給出)作為表達式進行計算。
在運行時,
isinstance(x, T)
將引發TypeError
。 一般來說,isinstance()
和issubclass()
不應該與類型一起使用。
然而, PEP-526在“非目標”中說:
雖然該提案附帶了用於運行時檢索注解的
typing.get_type_hints
標准庫函數的擴展,但變量注解不是為運行時類型檢查而設計的。 必須開發第三方軟件包來實現此類功能。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.