Класс Python, доступный по итератору и индексу - PullRequest
43 голосов
/ 19 марта 2011

Возможно, это вопрос n00b, но в настоящее время у меня есть класс, который реализует итератор, поэтому я могу сделать что-то вроде

for i in class():

но я хочу иметь доступ к классу по индексу так же, как

class()[1]

Как я могу это сделать?

Спасибо!

Ответы [ 2 ]

68 голосов
/ 06 января 2015

Текущий принятый ответ от @Ignacio Vazquez-Abrams достаточен. Тем не менее, другие, заинтересованные в этом вопросе, могут захотеть унаследовать свой класс от абстрактного базового класса (ABC) (например, найденного в стандартном модуле collections.abc). Это делает несколько вещей (, возможно, есть и другие ):

  • гарантирует, что все методы, необходимые для обработки вашего объекта "как ____", существуют
  • это самодокументирование, потому что кто-то, читающий ваш код, может мгновенно узнать, что вы намерены «вести себя как ____».
  • позволяет isinstance(myobject,SomeABC) работать правильно.
  • часто предоставляет методы автоматически, поэтому нам не нужно определять их самим

(Обратите внимание, что, помимо вышеизложенного, создание собственного ABC может позволить вам проверить наличие определенного метода или набора методов в любом объекте, и на основании этого объявить этот объект подклассом ABC, , даже если объект не наследуется от ABC напрямую . См. этот ответ для получения дополнительной информации. )


Пример: реализовать класс только для чтения, list, используя ABC

Теперь в качестве примера давайте выберем и реализуем ABC для класса в исходном вопросе. Есть два требования:

  1. класс повторяется
  2. доступ к классу по индексу

Очевидно, этот класс будет своего рода коллекцией. Итак, что мы сделаем, это посмотрим на наше меню 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!

Работает!

50 голосов
/ 19 марта 2011

Реализуйте методы __iter__() и __getitem__() и др.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...