Текущий принятый ответ от @Ignacio Vazquez-Abrams достаточен. Тем не менее, другие, заинтересованные в этом вопросе, могут захотеть унаследовать свой класс от абстрактного базового класса (ABC
) (например, найденного в стандартном модуле collections.abc
). Это делает несколько вещей (, возможно, есть и другие ):
- гарантирует, что все методы, необходимые для обработки вашего объекта "как ____", существуют
- это самодокументирование, потому что кто-то, читающий ваш код, может мгновенно узнать, что вы намерены «вести себя как ____».
- позволяет
isinstance(myobject,SomeABC)
работать правильно.
- часто предоставляет методы автоматически, поэтому нам не нужно определять их самим
(Обратите внимание, что, помимо вышеизложенного, создание собственного ABC
может позволить вам проверить наличие определенного метода или набора методов в любом объекте, и на основании этого объявить этот объект подклассом ABC
, , даже если объект не наследуется от ABC
напрямую . См. этот ответ для получения дополнительной информации. )
Пример: реализовать класс только для чтения, list
, используя ABC
Теперь в качестве примера давайте выберем и реализуем ABC
для класса в исходном вопросе. Есть два требования:
- класс повторяется
- доступ к классу по индексу
Очевидно, этот класс будет своего рода коллекцией. Итак, что мы сделаем, это посмотрим на наше меню collection
ABC , чтобы найти соответствующий ABC
(обратите внимание, что есть также numeric
ABC ). Соответствующее ABC
зависит от того, какие абстрактные методы мы хотим использовать в нашем классе.
Мы видим, что Iterable
- это то, что нам нужно, если мы хотим использовать метод __iter__()
, который нам нужен для того, чтобы делать такие вещи, как for o in myobject:
. Однако Iterable
не включает в себя метод __getitem__()
, который нам нужен для того, чтобы делать такие вещи, как myobject[i]
. Поэтому нам нужно использовать другой ABC
.
В меню абстрактных базовых классов collections.abc
мы видим, что Sequence
является самым простым ABC
, чтобы предложить нам необходимую функциональность. И - посмотрите на это - мы получаем Iterable
функциональность в качестве смешанного метода - что означает, что нам не нужно определять его самим - бесплатно! Мы также получаем __contains__
, __reversed__
, index
и count
. Что, если задуматься, это все, что должно быть включено в любой индексированный объект. Если бы вы забыли включить их, пользователи вашего кода (включая, возможно, самих себя!) Могли бы быть довольно раздражены (я знаю, что я буду).
Однако есть второй ABC
, который также предлагает эту комбинацию функций (итеративную и доступную для []
): a Mapping
. Какой из них мы хотим использовать?
Напомним, что необходимо иметь возможность доступа к объекту по индексу (например, list
или tuple
), т. Е. не по ключу (как у dict
). Поэтому мы выбираем Sequence
вместо Mapping
.
Боковая панель: Важно отметить, что Sequence
только для чтения (как и Mapping
), поэтому он не позволит нам делать такие вещи, как myobject[i] = value
или random.shuffle(myobject)
. Если мы хотим иметь возможность делать такие вещи, нам нужно продолжить вниз по меню ABC
с и использовать MutableSequence
(или MutableMapping
), который потребует реализации нескольких дополнительных методов.
Пример кода
Теперь мы можем сделать наш класс. Мы определили его, и он унаследован от Sequence
.
from collections.abc import Sequence
class MyClass(Sequence):
pass
Если мы попытаемся использовать его, интерпретатор скажет нам, какие методы нам нужно реализовать, прежде чем его можно будет использовать (обратите внимание, что эти методы также перечислены на странице документации Python):
>>> myobject = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods __getitem__, __len__
Это говорит нам о том, что, если мы продолжим реализацию __getitem__
и __len__
, мы сможем использовать наш новый класс. Мы можем сделать это так в Python 3:
from collections.abc import Sequence
class MyClass(Sequence):
def __init__(self,L):
self.L = L
super().__init__()
def __getitem__(self, i):
return self.L[i]
def __len__(self):
return len(self.L)
# Let's test it:
myobject = MyClass([1,2,3])
try:
for idx,_ in enumerate(myobject):
print(myobject[idx])
except Exception:
print("Gah! No good!")
raise
# No Errors!
Работает!