У меня есть такой абстрактный класс в Python
class Sinker(ABC):
@abstractmethod
def batch_write(self, data) -> None:
pass
Ожидается, что конкретный класс будет записывать данные в некоторые облачные базы данных. Я хотел написать абстрактный класс таким образом, чтобы гарантировать, что конкретные классы всегда реализуют метод batch_write()
.
Однако я не уверен, какие подсказки типа мне следует вставить data
, потому что один из моих конкретных классов ожидает List[str]
, а другой конкретный класс ожидает List[dict]
.
Вот лишь некоторые варианты, которые пока пришли мне в голову.
Any
List[Any]
List[str|dict]
Что было бы лучшим способом сделать это? Есть ли руководство по стилю для этой ситуации?
Я опробовал все три варианта, и, поскольку это всего лишь подсказки по типу, это не вызовет у меня никаких проблем. Но я просто хочу убедиться, что пишу коды, соответствующие лучшим практикам ООП, если таковые имеются.
Спасибо за напоминание! Я исправил метод и посмотрю общую ссылку.
Я бы сказал, что Any
здесь является рекомендуемым решением. Поскольку это abstractmethod
, люди не могут напрямую использовать/реализовать метод batch_write
из Sinker
, и у него есть оператор pass
, поэтому, на мой взгляд, вам не нужно быть очень конкретным. Я бы реализовал более конкретные подсказки типов в методе batch_write
в классах, которые наследуются от Sinker
.
Кроме того, если вы используете List[dict | str]
в качестве подсказки типа, вы будете вынуждены обновлять эту подсказку типа каждый раз, когда реализуете новый дочерний класс, имеющий метод batch_write
, где data
имеет другой тип (например, List[int]
). Просто будьте как можно более общими в абстрактном классе, чтобы избежать изменения подписи. Но если вы уверены, что это всегда будет list
, вы можете выбрать List[Any
]`. (См. команду под вашим вопросом от Lrx, она мне понравилась).
«невозможно напрямую использовать/реализовать метод пакетной записи из Sinker». Ну, вы можете вызвать его для объекта Sinker, не обязательно зная, какой конкретный подкласс используется. В этом вся суть абстрактного метода. И весь смысл подсказки типа возвращаемого значения в том, что вы знаете, что получите в ответ, когда сделаете это.
Хорошо, но вопрос не в типе возвращаемого значения, а abstractmethod
здесь явно предназначен для указания в дочерних классах. Возможно, мне следовало написать это так: «Вы не можете создать экземпляр абстрактного класса». Возможно, так было бы лучше, но тем не менее в этом примере абстрактный метод ничего не делает. Это имело бы огромное значение, если бы в этом была какая-то реальная логика. Но тогда я бы также предложил использовать более конкретные подсказки типов для аргументов.
Я должен был сказать: «Цель подсказки типа параметра состоит в том, чтобы указать, какой аргумент ожидается, когда вы это делаете». Re: «Абстрактный метод здесь явно предназначен для указания в дочерних классах». Ожидается, что все абстрактные методы будут реализованы в подклассах: вот что такое абстрактный метод.
Спасибо! Мне нравится тот факт, что List[dict | str]
может заставить меня изменить абстрактный класс всякий раз, когда реализуется новый конкретный класс. Я более уверенно уберу третий вариант.
Вы можете создать Sinker
универсальный класс, чтобы можно было параметризовать тип элементов в списке, передаваемом в batch_write
:
from abc import ABC, abstractmethod
class Sinker[T](ABC):
@abstractmethod
def batch_write(self, data: list[T]) -> None:
...
class StrSinker(Sinker[str]):
def batch_write(self, data: list[str]) -> None:
pass
class DictSinker(Sinker[dict]):
def batch_write(self, data: list[dict]) -> None:
pass
Демо с Pyright
Таким образом, средство проверки типов сможет обнаружить для вас неправильную подсказку типа. Например, с:
class StrSinker(Sinker[str]):
def batch_write(self, data: list[int]) -> None:
pass
Пайрайт подал следующую жалобу:
Method "batch_write" overrides class "Sinker" in an incompatible manner
Parameter 2 type mismatch: base parameter is type "list[str]", override parameter is type "list[int]"
"list[str]" is incompatible with "list[int]"
Type parameter "_T@list" is invariant, but "str" is not the same as "int"
Consider switching from "list" to "Sequence" which is covariant (reportIncompatibleMethodOverride)
Демо с Pyright
Поскольку метод batch_write
обычно не определен для всех экземпляров Sinker
, его неоднозначное определение (Any
или общее) в Sinker
не добавит ясности в том, как его следует использовать.
Людям все равно придется просматривать подклассы, чтобы определить, приемлемы ли их аргументы. Поэтому я не думаю, что определение его на Sinker
что-то добавляет.
Вместо этого подождите, пока подкласс, в котором метод (включая его аргументы) не будет полностью определен, прежде чем создавать абстрактный метод, даже если для этого потребуется добавить еще один абстрактный промежуточный метод, например.
class SinkerStringBatchWritable(Sinker):
@abstractmethod
def batch_write(self, data: list[str]) -> None:
pass
В такой ситуации ваш выбор зависит от того, какой уровень абстракции вы хотите для своего абстрактного базового класса. Если вы точно знаете, что вашим аргументом будет список, то
List[Any]
кажется более подходящим. у вас первый и третий варианты либо слишком расплывчаты, либо конкретны. Также не забывайте про self вdef batch_write(self, data)
и не объявляйте его как статический метод. Наконец, несмотря на то, что я ответил на него, ваш вопрос не является «проблемой», а скорее советом, поэтому подумайте о том, чтобы взглянуть на Как задавать