Generic[T] base class - 如何从实例中获取 T 的类型?
Generic[T] base class - how to get type of T from within instance?
假设您有一个 Python class 继承自 Generic[T]。有什么方法可以获取从 class/instance?
中传入的实际类型
例如,
from typing import TypeVar, Type
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
my_type = T # this is wrong!
print( "I am {0}".format(my_type) )
Test[int]().hello() # should print "I am int"
在 ,建议类型 arg 出现在类型的 args 字段中。事实上,
print( str( Test[int].__args__ ) )
将打印 (,)。但是,我似乎无法直接从实例中访问它,例如替换
my_type = self.__class__.__args__ # this is also wrong (None)
似乎没有成功。
谢谢
API 不支持此功能。在有限的情况下,如果你愿意乱搞未记录的实现细节,你有时可以这样做,但它根本不可靠。
首先,mypy 不要求您在分配给通用类型变量时提供类型参数。你可以做像 x: Test[int] = Test()
这样的事情,Python 和 mypy 都不会抱怨。 mypy 推断类型参数,但在运行时使用 Test
而不是 Test[int]
。由于显式类型参数难以编写并且会带来性能损失,因此许多代码仅在注释中使用类型参数,而不是在运行时使用。
无法在运行时恢复从未在运行时提供的类型参数。
当在运行时提供类型参数时,实现 确实 当前尝试保留此信息,但仅在完全未记录的内部属性中,如有更改,恕不另行通知,并且甚至这个属性也可能不存在。具体来说,当你打电话给
Test[int]()
,新对象的 class 是 Test
而不是 Test[int]
,但是 typing
实现试图设置
obj.__orig_class__ = Test[int]
关于新对象。如果它不能设置 __orig_class__
(例如,如果 Test
使用 __slots__
),那么它会捕获 AttributeError 并放弃。
__orig_class__
是在 Python 3.5.3 中引入的;它不存在于 3.5.2 及更低版本中。 typing
中没有任何内容实际使用 __orig_class__
.
__orig_class__
分配的时间因 Python 版本而异,但目前,它是 set 在正常对象构造已经完成之后。在 __init__
或 __new__
期间,您将无法检查 __orig_class__
。
这些实现细节是截至 CPython 3.8.2.
的最新版本
__orig_class__
是一个实现细节,但至少在 Python 3.8 上,您不必访问任何其他实现细节来获取类型参数。 Python 3.8 引入了 typing.get_args
,其中 returns 是 typing
类型参数的元组,或 ()
无效参数。 (是的,从 Python 3.5 到 3.8 一直没有 public API。)
例如,
typing.get_args(Test[int]().__orig_class__) == (int,)
如果 __orig_class__
存在并且您愿意访问它,那么 __orig_class__
和 get_args
一起提供您正在寻找的内容。
您可以使用 self.__orig_class__
:
from typing import TypeVar, Type, Generic
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
print( "I am {0}".format(self.__orig_class__.__args__[0].__name__))
Test[int]().hello()
# I am int
您完全可以使用其他东西。
# Define Wrapper Class
class Class():
# Define __getitem__ method to be able to use index
def __getitem__(self, type):
# Define Main Class
class Class():
__doc__ = f"""I am an {type.__name__} class"""
def __init__(self, value):
self.value: type = type(value)
# Return Class
return Class
# Set Class to an instance of itself to be able to use the indexing
Class = Class()
print(Class[int].__doc__)
print(Class[int](5.3).value)
如果需要,请使用它。您可以在整个 class 中使用类型变量,即使不使用 self.只是说,语法高亮器可能很难理解这类事情,因为它使用了他们并不真正关心的东西的 return,即使它是 class。至少对于pylance,因为那是我使用的。
如果您愿意稍微调整 class 实例化的语法,这是可能的。
import typing
T = typing.TypeVar('T')
class MyGenericClass(typing.Generic[T]):
def __init__(self, generic_arg: typing.Type[T]) -> None:
self._generic_arg = generic_arg
def print_generic_arg(self) -> None:
print(f"My generic argument is {self._generic_arg}")
def print_value(self, value: T) -> None:
print(value)
my_instance = MyGenericClass(int) # Note, NOT MyGenericClass[int]().
my_instance.print_generic_arg() # Prints "My generic argument is <class 'int'>".
reveal_type(my_instance) # Revealed type is MyGenericClass[builtins.int*].
# Note the asterisk, indicating that
# builtins.int was *inferred.*
my_instance.print_value(123) # OK, prints 123.
my_instance.print_value("abc") # Type-checking error, "abc" isn't an int.
与 一样,尝试在运行时从实例的类型中检索类型参数可能存在内在问题。部分原因是,实例甚至不一定以类型参数开头创建。
因此,这种方法通过从另一个角度解决问题来解决这个问题。我们要求调用者将类型 作为参数传递给 __init__
. 然后,而不是尝试从实例的类型 MyGenericClass[int]
,我们从 int
的运行时值开始,让类型检查器推断实例的类型是 MyGenericClass[int]
.
在更复杂的情况下,您可能需要通过冗余指定类型来帮助类型检查器。您有责任确保类型匹配。
possible_types = {"int": int, "str": str}
# Error: "Need type annotation".
my_instance = MyGenericClass(possible_types["int"])
# OK.
my_instance = MyGenericClass[int](possible_types["int"])
目前 (Python 3.10.3) 在 __init__
或 __new__
期间无法访问类型参数。
但是,可以访问 __init_subclass__
中的类型变量。这是一个有点不同的场景,但我认为它很有趣,可以分享。
from typing import Any, Generic, TypeVar, get_args
T = TypeVar("T")
class MyGenericClass(Generic[T]):
_type_T: Any
def __init_subclass__(cls) -> None:
cls._type_T = get_args(cls.__orig_bases__[0])[0] # type: ignore
class SomeBaseClass(MyGenericClass[int]):
def __init__(self) -> None:
print(self._type_T)
SomeBaseClass() # prints "<class 'int'>"
假设您有一个 Python class 继承自 Generic[T]。有什么方法可以获取从 class/instance?
中传入的实际类型例如,
from typing import TypeVar, Type
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
my_type = T # this is wrong!
print( "I am {0}".format(my_type) )
Test[int]().hello() # should print "I am int"
在
print( str( Test[int].__args__ ) )
将打印 (
my_type = self.__class__.__args__ # this is also wrong (None)
似乎没有成功。
谢谢
API 不支持此功能。在有限的情况下,如果你愿意乱搞未记录的实现细节,你有时可以这样做,但它根本不可靠。
首先,mypy 不要求您在分配给通用类型变量时提供类型参数。你可以做像 x: Test[int] = Test()
这样的事情,Python 和 mypy 都不会抱怨。 mypy 推断类型参数,但在运行时使用 Test
而不是 Test[int]
。由于显式类型参数难以编写并且会带来性能损失,因此许多代码仅在注释中使用类型参数,而不是在运行时使用。
无法在运行时恢复从未在运行时提供的类型参数。
当在运行时提供类型参数时,实现 确实 当前尝试保留此信息,但仅在完全未记录的内部属性中,如有更改,恕不另行通知,并且甚至这个属性也可能不存在。具体来说,当你打电话给
Test[int]()
,新对象的 class 是 Test
而不是 Test[int]
,但是 typing
实现试图设置
obj.__orig_class__ = Test[int]
关于新对象。如果它不能设置 __orig_class__
(例如,如果 Test
使用 __slots__
),那么它会捕获 AttributeError 并放弃。
__orig_class__
是在 Python 3.5.3 中引入的;它不存在于 3.5.2 及更低版本中。 typing
中没有任何内容实际使用 __orig_class__
.
__orig_class__
分配的时间因 Python 版本而异,但目前,它是 set 在正常对象构造已经完成之后。在 __init__
或 __new__
期间,您将无法检查 __orig_class__
。
这些实现细节是截至 CPython 3.8.2.
的最新版本__orig_class__
是一个实现细节,但至少在 Python 3.8 上,您不必访问任何其他实现细节来获取类型参数。 Python 3.8 引入了 typing.get_args
,其中 returns 是 typing
类型参数的元组,或 ()
无效参数。 (是的,从 Python 3.5 到 3.8 一直没有 public API。)
例如,
typing.get_args(Test[int]().__orig_class__) == (int,)
如果 __orig_class__
存在并且您愿意访问它,那么 __orig_class__
和 get_args
一起提供您正在寻找的内容。
您可以使用 self.__orig_class__
:
from typing import TypeVar, Type, Generic
T = TypeVar('T')
class Test(Generic[T]):
def hello(self):
print( "I am {0}".format(self.__orig_class__.__args__[0].__name__))
Test[int]().hello()
# I am int
您完全可以使用其他东西。
# Define Wrapper Class
class Class():
# Define __getitem__ method to be able to use index
def __getitem__(self, type):
# Define Main Class
class Class():
__doc__ = f"""I am an {type.__name__} class"""
def __init__(self, value):
self.value: type = type(value)
# Return Class
return Class
# Set Class to an instance of itself to be able to use the indexing
Class = Class()
print(Class[int].__doc__)
print(Class[int](5.3).value)
如果需要,请使用它。您可以在整个 class 中使用类型变量,即使不使用 self.只是说,语法高亮器可能很难理解这类事情,因为它使用了他们并不真正关心的东西的 return,即使它是 class。至少对于pylance,因为那是我使用的。
如果您愿意稍微调整 class 实例化的语法,这是可能的。
import typing
T = typing.TypeVar('T')
class MyGenericClass(typing.Generic[T]):
def __init__(self, generic_arg: typing.Type[T]) -> None:
self._generic_arg = generic_arg
def print_generic_arg(self) -> None:
print(f"My generic argument is {self._generic_arg}")
def print_value(self, value: T) -> None:
print(value)
my_instance = MyGenericClass(int) # Note, NOT MyGenericClass[int]().
my_instance.print_generic_arg() # Prints "My generic argument is <class 'int'>".
reveal_type(my_instance) # Revealed type is MyGenericClass[builtins.int*].
# Note the asterisk, indicating that
# builtins.int was *inferred.*
my_instance.print_value(123) # OK, prints 123.
my_instance.print_value("abc") # Type-checking error, "abc" isn't an int.
与
因此,这种方法通过从另一个角度解决问题来解决这个问题。我们要求调用者将类型 作为参数传递给 __init__
. 然后,而不是尝试从实例的类型 MyGenericClass[int]
,我们从 int
的运行时值开始,让类型检查器推断实例的类型是 MyGenericClass[int]
.
在更复杂的情况下,您可能需要通过冗余指定类型来帮助类型检查器。您有责任确保类型匹配。
possible_types = {"int": int, "str": str}
# Error: "Need type annotation".
my_instance = MyGenericClass(possible_types["int"])
# OK.
my_instance = MyGenericClass[int](possible_types["int"])
目前 (Python 3.10.3) 在 __init__
或 __new__
期间无法访问类型参数。
但是,可以访问 __init_subclass__
中的类型变量。这是一个有点不同的场景,但我认为它很有趣,可以分享。
from typing import Any, Generic, TypeVar, get_args
T = TypeVar("T")
class MyGenericClass(Generic[T]):
_type_T: Any
def __init_subclass__(cls) -> None:
cls._type_T = get_args(cls.__orig_bases__[0])[0] # type: ignore
class SomeBaseClass(MyGenericClass[int]):
def __init__(self) -> None:
print(self._type_T)
SomeBaseClass() # prints "<class 'int'>"