Рассмотрим следующий код:
from typing import Protocol, ClassVar, Any
class Tool(Protocol):
t: ClassVar
# def f(self, params: self.t) -> Any:
# NameError: name 'self' is not defined
def f(self, params) -> Any:
pass
class Sum:
t: ClassVar = list[int]
def f(self, params: list[int]) -> int:
return sum(params)
def use_tool(tool: Tool, params: Any) -> Any:
return tool.f(params)
s = Sum()
print(use_tool(s, [1, 2]))
Я хотел бы определить протокол Tool так, чтобы такие классы, как Sum, могли реализовать его с помощью определенного t, который хранит объект типа/класса, чтобы в таких методах, как Sum.f(), params было бы ограничено использование типа t при проверке типа. Как я могу это сделать?
Я пытался использовать self.t для ссылки на t в аннотации типа, но это не работает, равно как и замена self на typing.Self.
Я думаю, что сохранение типа параметра для f() было бы полезно в том случае, если, предположим, t хранит pydantic.BaseModel (что означает, что t имеет тип ModelMetaclass), тогда в таких функциях, как use_tool, я могу посмотреть схему t через t.model_dump_json(). Кроме того, use_tool() может принимать строку JSON в качестве входных данных и анализировать ее в соответствующую модель через t.model_validate_json() перед вызовом Tool.f.
@Dunes Да, я подумывал о том, чтобы сделать Tool универсальным, но я намерен сделать так, чтобы такие классы, как Sum, реализовывали протокол Tool, одновременно раскрывая Sum.t. Я должен прояснить это в своем вопросе.
@oskar Первое твое предложение может показаться излишне резким. Предупреждение средств проверки типов о типе параметра/аргумента также считается «принудительным».
Кажется, вы можете использовать общий класс: docs.python.org/3.11/library/…
@oskar Я имел в виду «силу» в том смысле, в котором упоминался InSync. Я изменил свой вопрос, чтобы уточнить это. Это похоже на общие классы, где некоторые переменные должны иметь один и тот же тип.
@KamilCuk Я хотел бы также сохранить сам тип в классе. Было бы приемлемым решением, если бы Generic[T] позволял мне хранить сам T, например let self.t = T.
Извините, этого не должно было случиться. Вы можете использовать from __future__ import annotations. Затем вы можете заменить типы строками, например. «Сумма». Возможно, это работает и в вашем случае с «Sum.t».






Чтобы использовать в качестве подсказки типа, t должен быть TypeAlias, но в этом случае он должен быть инициализирован одновременно с его определением:
(playground links: Mypy , Pyright)
class Tool(Protocol):
t: TypeAlias = list[int] # pyright => fine
# mypy => error: Type aliases are prohibited in protocol bodies
def f(self, params: 'Tool.t') -> Any: # both => fine
reveal_type(params) # both => list[int]
(Разрешены ли псевдонимы типов внутри Protocols, неясно; в спецификации об этом ничего не сказано.)
Вместо этого сделайте Tool общим:
(playground links: Mypy , Pyright)
class Tool[T](Protocol):
t: type[T]
def f(self, params: T) -> Any: ...
Затем его можно использовать как:
class Sum:
# "type[list[int]]" is necessary for Mypy
# to understand that it is not a class-level type alias
t: type[list[int]] = list[int]
def f(self, params: list[int]) -> int: ...
def use_tool[T](tool: Tool[T], params: T) -> Any: ...
s = Sum()
use_tool(s, [1, 2]) # fine
У этого есть еще одно преимущество: Tool также может быть универсальным по сравнению с возвращаемым типом .f().
(playground links: Mypy , Pyright)
class Tool[T, R](Protocol):
t: type[T]
def f(self, params: T) -> R: ...
...
def use_tool[T, R](tool: Tool[T, R], params: T) -> R: ...
s = Sum()
reveal_type(use_tool(s, [1, 2])) # int
Что касается части NameError (которая не будет проблемой при таком решении), см. этот вопрос.
Вы не можете сделать это так, как предложено.
ClassVar, как следует из названия, является переменной. Возможно, вы никогда не изменитеSum.t, но любой инструмент статической типизации должен учитывать такую возможность. Вы рассматривали возможность созданияToolдженерика?