英文:
How to test __annotations__ member has same type as generic class parameter?
问题
我想能够只选择已注释的成员,这些成员与基类中泛型参数的类型相同:
from typing import Generic, TypeVar
T = TypeVar("T")
class MarkFinder(Generic[T]):
def __init_subclass__(cls, **kwargs):
cls.marked = tuple(
name for name, annotated_type in cls.__annotations__.items()
if some_condition(annotated_type)
)
这样,如果我继承:
T2 = TypeVar("T2")
class Inheritor(MarkFinder[T2]):
a: T2
b: int
c: T2
那么 Inheritor.marked
应该是 ('a', 'c')
。
我尝试用以下替换 some_condition(annotated_type)
:
cls.__parameters__[0] is annotated_type
或 cls.__parameters__[0] == annotated_type
但即使它们具有相同的名称,类型也不相等。
什么是正确的条件?或者这是不可能的?
英文:
I want to be able to select only annotated members that have the same type as the parameter of generic in this base class:
from typing import Generic, TypeVar
T = TypeVar("T")
class MarkFinder(Generic[T]):
def __init_subclass__(cls, **kwargs):
cls.marked = tuple(
name for name, annotated_type in cls.__annotations__.items()
if some_condition(annotated_type)
)
So that if I inherit:
T2 = TypeVar("T2")
class Inheritor(MarkFinder[T2]):
a: T2
b: int
c: T2
Then Inheritor.marked
should just be ('a', 'c')
.
I have tried to replace some_condition(annotated_type)
with:
cls.__parameters__[0] is annotated_type
or cls.__parameters__[0] == annotated_type
but even though they have the same names, the types are not equal.
What is the correct condition? Or is this impossible?
答案1
得分: 1
这是可能的,但有一些注意事项。该方法的关键部分以及一些需要记住的事项在以下帖子中有解释,因此建议您首先阅读它:
https://stackoverflow.com/questions/73746553
TL;DR 是从__orig_bases__
中获取来自_original_基类的类型参数(它将是泛型别名类型),然后与注释进行比较(以确定其是否相同)。
根据您提出问题的方式,我假设您只想将此应用于类型变量,而不是_特定的_类型参数。以下是您可以执行此操作的方式:
from typing import Any, ClassVar, Generic, TypeVar, get_args, get_origin
T = TypeVar("T")
class MarkFinder(Generic[T]):
marked: ClassVar[tuple[str, ...]] = ()
@classmethod
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
for base in cls.__orig_bases__: # type: ignore[attr-defined]
origin = get_origin(base)
if origin is None or not issubclass(origin, MarkFinder):
continue
type_arg = get_args(base)[0]
if not isinstance(type_arg, TypeVar):
return # do not touch non-generic subclasses
cls.marked += tuple(
name for name, annotation in cls.__annotations__.items()
if annotation is type_arg
)
用法演示:
T2 = TypeVar("T2")
class Child(MarkFinder[T2]):
a: T2
b: int
c: T2
T3 = TypeVar("T3")
class GrandChild(Child[T3]):
d: T3
e: str
class SpecificDescendant1(GrandChild[int]):
f: int
class SpecificDescendant2(GrandChild[str]):
f: float
print(Child.marked) # ('a', 'c')
print(GrandChild.marked) # ('a', 'c', 'd')
print(SpecificDescendant1.marked) # ('a', 'c', 'd')
print(SpecificDescendant2.marked) # ('a', 'c', 'd')
检查type_arg
不是TypeVar
实例的部分很重要。如果没有这个检查,那么SpecificDescendant1
子类也会在其marked
元组中包含'f'
。(如果这是您想要的,请从基类的__init_subclass__
方法中删除该检查。)
英文:
This is possible, but with a few caveats. The crux of the approach as well as some of the things to keep in mind are explained in the following post, so I suggest you read it first:
https://stackoverflow.com/questions/73746553
The TL;DR is to grab the the type argument from the original base class (which will be a generic alias type) from __orig_bases__
and compare the annotations against that (for identity).
From the way you phrased your question, I assume you only want this to apply to type variables and not to specific type arguments. Here is how you could do it:
from typing import Any, ClassVar, Generic, TypeVar, get_args, get_origin
T = TypeVar("T")
class MarkFinder(Generic[T]):
marked: ClassVar[tuple[str, ...]] = ()
@classmethod
def __init_subclass__(cls, **kwargs: Any) -> None:
super().__init_subclass__(**kwargs)
for base in cls.__orig_bases__: # type: ignore[attr-defined]
origin = get_origin(base)
if origin is None or not issubclass(origin, MarkFinder):
continue
type_arg = get_args(base)[0]
if not isinstance(type_arg, TypeVar):
return # do not touch non-generic subclasses
cls.marked += tuple(
name for name, annotation in cls.__annotations__.items()
if annotation is type_arg
)
Usage demo:
T2 = TypeVar("T2")
class Child(MarkFinder[T2]):
a: T2
b: int
c: T2
T3 = TypeVar("T3")
class GrandChild(Child[T3]):
d: T3
e: str
class SpecificDescendant1(GrandChild[int]):
f: int
class SpecificDescendant2(GrandChild[str]):
f: float
print(Child.marked) # ('a', 'c')
print(GrandChild.marked) # ('a', 'c', 'd')
print(SpecificDescendant1.marked) # ('a', 'c', 'd')
print(SpecificDescendant2.marked) # ('a', 'c', 'd')
The check for the type_arg
not being a TypeVar
instance is important. Without it, that SpecificDescendant1
subclass would also have 'f'
in its marked
tuple. (If that is what you want, just remove that check from the base class' __init_subclass__
method.)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论