0

Позвольте классу вести себя как список в Python

18

У меня есть класс, который по сути является коллекцией/списком элементов. Но я хочу добавить некоторые дополнительные функции к этому списку. Мне нужно следующее:

  • У меня есть экземпляр li = MyFancyList(). Переменная li должна вести себя как список всякий раз, когда я использую её как список: [e for e in li], li.expand(...), for e in li.
  • Кроме того, он должен иметь специальные функции, такие как li.fancyPrint(), li.getAMetric(), li.getName().

В данный момент я использую следующий подход:

class MyFancyList:
    def __iter__(self): 
        return self.li 
    def fancyFunc(self):
        # выполняет что-то "крутое"

Этот способ подходит для использования в качестве итератора, например, [e for e in li], но у меня отсутствует полноценное поведение списка, такое как li.expand(...).

Первое предположение заключается в том, чтобы унаследовать от list в классе MyFancyList. Но является ли это рекомендованным "питоническим" способом? Если да, то на что стоит обратить внимание? Если нет, то какой был бы лучший подход?

4 ответ(ов)

0

Наиболее простой способ решить эту задачу — унаследоваться от класса list:

class MyFancyList(list):
    def fancyFunc(self):
        # сделать что-то интересное

Теперь вы можете использовать тип MyFancyList как обычный список и пользоваться его специфическими методами.

Однако стоит отметить, что наследование создает сильную связь между вашим объектом и list. Реализованный вами подход по сути является прокси-объектом. Способ использования будет сильно зависеть от того, как именно вы планируете применять объект. Если он должен быть списком, то наследование, вероятно, является хорошим выбором.


ИЗМЕНЕНИЕ: как указал @acdr, некоторые методы, возвращающие копию списка, должны быть переопределены, чтобы они возвращали MyFancyList, а не list.

Вот простой способ это реализовать:

class MyFancyList(list):
    def fancyFunc(self):
        # сделать что-то интересное
    def __add__(self, *args, **kwargs):
        return MyFancyList(super().__add__(*args, **kwargs))
0

Если вы не хотите переопределять каждый метод класса list, я предлагаю следующий подход:

class MyList:
    def __init__(self, list_):
        self.li = list_

    def __getattr__(self, method):
        return getattr(self.li, method)

С помощью этого кода методы, такие как append, extend и прочие, будут работать без дополнительных усилий. Однако учтите, что магические методы (например, __len__, __getitem__ и т. д.) не будут функционировать в этом случае, поэтому вам следует как минимум переопределить их следующим образом:

class MyList:
    def __init__(self, list_):
        self.li = list_

    def __getattr__(self, method):
        return getattr(self.li, method)

    def __len__(self):
        return len(self.li)

    def __getitem__(self, item):
        return self.li[item]

    def fancyPrint(self):
        # делайте, что хотите...

Обратите внимание, что если вы хотите переопределить метод класса list (например, extend), вы можете просто объявить свой собственный метод, чтобы вызов не проходил через __getattr__. Например:

class MyList:
    def __init__(self, list_):
        self.li = list_

    def __getattr__(self, method):
        return getattr(self.li, method)

    def __len__(self):
        return len(self.li)

    def __getitem__(self, item):
        return self.li[item]

    def fancyPrint(self):
        # делайте, что хотите...

    def extend(self, list_):
        # ваша версия метода extend
0

Основываясь на двух примерах методов, которые вы привели в своем посте (fancyPrint, findAMetric), не кажется, что вам нужно хранить какое-либо дополнительное состояние в ваших списках. Если это действительно так, наилучшим вариантом будет просто объявить эти методы как свободные функции и вовсе отказаться от иерархии классов; это полностью избавит вас от проблем типа list против UserList, хрупких краевых случаев, таких как возвращаемые типы для __add__, неожиданных проблем Лискова и т. д. Вместо этого вы можете писать свои функции, писать для них модульные тесты и быть уверенными, что всё будет работать именно так, как задумано.

Кроме того, это имеет дополнительное преимущество: ваши функции будут работать с любыми итерабельными типами (такими как генераторные выражения) без каких-либо дополнительных усилий.

0

Вы можете создать собственный класс, наследующий от списка в Python, и переопределить методы append и __setitem__, чтобы ограничить элементы списка только целыми числами. Ваш код выглядит хорошо, но я бы предложил немного улучшить его для большей читаемости и следования стандартам кодирования PEP 8. Вот пример ваш код с некоторыми изменениями:

class ListInteger(list):
    def __init__(self, *args):
        if all(isinstance(i, int) for i in args[0]):
            super().__init__(args[0])
        else:
            raise TypeError('Извините, допускаются только целые числа')

    def __setitem__(self, idx, value):
        if isinstance(value, int):
            super().__setitem__(idx, value)
        else:
            raise TypeError('Извините, допускаются только целые числа')

    def append(self, __object) -> None:
        if isinstance(__object, int):
            super().append(__object)
        else:
            raise TypeError('Извините, допускаются только целые числа')

Вот основные моменты, которые вы можете учесть:

  1. Я использовал isinstance() вместо проверки типа с помощью type(). Это считается более "питоническим" стилем.
  2. Сообщения об ошибках я слегка изменил, чтобы они были более информативными.
  3. Обратите внимание на корректное выравнивание. В Python важен отступ, поэтому убедитесь, что ваш код аккуратно отформатирован.

Теперь ваш класс будет успешно ограничивать список только целыми числами при использовании методов append и __setitem__.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь