Пользовательские списки Python: наследование от list против UserList

Оглавление

В какой-то момент вашего приключения по написанию кода на Python вам может понадобиться создать пользовательские классы, похожие на списки с измененным поведением, новыми функциональными возможностями или и тем, и другим. Чтобы сделать это в Python, вы можете наследовать от абстрактного базового класса , создать подкласс встроенного класса list напрямую или наследовать от UserList, который находится в collections модуль.

В этом руководстве вы узнаете, как:

  • Создавайте пользовательские классы, подобные спискам, наследуя от встроенного list класса
  • Создайте пользовательские классы, подобные спискам, путем создания подклассов UserList из модуля collections

Вы также напишете несколько примеров, которые помогут вам решить, какой родительский класс, list или UserList, использовать при создании пользовательских списков классов.

Чтобы извлечь максимальную пользу из этого руководства, вы должны быть знакомы со встроенным классом Python list и его стандартными функциями. Вам также необходимо знать основы объектно-ориентированного программирования и понимать, как наследование работает в Python.

Скачать бесплатно: Нажмите здесь, чтобы загрузить исходный код, который вы будете использовать для создания пользовательских классов, похожих на списки.

Создание спископодобных классов в Python

Встроенный list класс является основным типом данных в Python. Списки полезны во многих ситуациях и имеют множество практических примеров использования. В некоторых из этих вариантов использования стандартная функциональность Python list может оказаться недостаточной, и вам может потребоваться создать пользовательские классы, подобные спискам, для решения данной проблемы.

Обычно вы найдете по крайней мере две причины для создания пользовательских классов, похожих на списки:

  1. Расширение обычного списка за счет добавления новых функциональных возможностей
  2. Изменение функциональности стандартного списка

Вы также можете столкнуться с ситуациями, в которых вам потребуется расширить и стандартные функциональные возможности списка.

В зависимости от ваших конкретных потребностей и уровня квалификации вы можете использовать несколько стратегий для создания собственных классов, похожих на списки. Вы можете:

  • Наследуется от соответствующего абстрактного базового класса, такого как MutableSequence
  • Наследуется непосредственно от встроенного класса Python list
  • Подкласс UserList из collections

Примечание: В объектно-ориентированном программировании обычно используются глаголы наследовать и подклассы взаимозаменяемы.

При выборе подходящей стратегии необходимо учитывать несколько факторов. Продолжайте читать для получения более подробной информации.

Создание класса, подобного списку, на основе абстрактного Базового Класса

Вы можете создавать свои собственные классы, подобные спискам, наследуя от соответствующего абстрактного базового класса (ABC), например MutableSequence. Этот ABC предоставляет общие реализации большинства методов list, за исключением .__getitem__(), .__setitem__(), .__delitem__, .__len__(), и .insert(). Таким образом, при наследовании от этого класса вам придется реализовать эти методы самостоятельно.

Написание собственной реализации для всех этих специальных методов - это изрядный объем работы. Это чревато ошибками и требует глубоких знаний Python и его модели данных . Это также может привести к проблемам с производительностью, поскольку вы будете писать методы на чистом Python.

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

Основное преимущество этой стратегии создания спископодобных классов заключается в том, что родительский класс ABC предупредит вас, если вы пропустите какие-либо требуемые методы в вашей пользовательской реализации.

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

В этом руководстве вы сосредоточитесь на создании спископодобных классов путем наследования от встроенного класса list и класса UserList из модуля стандартной библиотеки collections. Эти стратегии кажутся самыми быстрыми и практичными.

Наследование от встроенного класса Python list

Долгое время было невозможно наследовать напрямую от типов Python, реализованных в C. В Python 2.2 исправлена эта проблема. Теперь вы можете подклассифицировать встроенные типы, включая list. Это изменение принесло подклассам несколько технических преимуществ, поскольку теперь они:

Первый элемент в этом списке может быть требованием для кода на C, который ожидает наличия встроенного класса на Python. Второй элемент позволяет добавлять новые функциональные возможности поверх стандартного поведения в списке. Наконец, третий пункт позволит вам ограничить атрибуты подкласса только теми атрибутами, которые предопределены в .__slots__.

Чтобы начать работу и начать создавать пользовательские классы, подобные спискам, скажем, что вам нужен список, который автоматически сохраняет все свои элементы в виде строк. Предполагая, что ваш пользовательский список будет хранить числа только в виде строк, вы можете создать следующий подкласс list:

# string_list.py

class StringList(list):
    def __init__(self, iterable):
        super().__init__(str(item) for item in iterable)

    def __setitem__(self, index, item):
        super().__setitem__(index, str(item))

    def insert(self, index, item):
        super().insert(index, str(item))

    def append(self, item):
        super().append(str(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            super().extend(other)
        else:
            super().extend(str(item) for item in other)


Ваш класс StringList напрямую относится к подклассам list, что означает, что он унаследует всю функциональность стандартного Python list. Поскольку вы хотите, чтобы в вашем списке элементы хранились в виде строк, вам необходимо изменить все методы, которые добавляют или изменяют элементы в базовом списке. К таким методам относятся следующие:

  • .__init__ инициализирует все новые экземпляры класса.
  • .__setitem__() позволяет присвоить новое значение существующему элементу, используя индекс элемента, как показано в a_list[index] = item.
  • .insert() позволяет вам вставить новый элемент в заданную позицию в базовом списке, используя индекс элемента.
  • .append() добавляет один новый элемент в конец основного списка.
  • .extend() добавляет ряд элементов в конец списка.

Другие методы, которые ваш класс StringList унаследовал от list, работают просто отлично, потому что они не добавляют и не обновляют элементы в вашем пользовательском списке.

Примечание: Если вы хотите, чтобы ваш класс StringList поддерживал объединение с помощью оператора plus (+), то вам также потребуется реализовать другие специальные методы, такие как .__add__(), .__radd__(), и .__iadd__().

Чтобы использовать StringList в своем коде, вы можете сделать что-то вроде этого:

>>> from string_list import StringList

>>> data = StringList([1, 2, 2, 4, 5])
>>> data
['1', '2', '2', '4', '5']

>>> data.append(6)
>>> data
['1', '2', '2', '4', '5', '6']

>>> data.insert(0, 0)
>>> data
['0', '1', '2', '2', '4', '5', '6']

>>> data.extend([7, 8, 9])
>>> data
['0', '1', '2', '2', '4', '5', '6', '7', '8', '9']

>>> data[3] = 3
>>> data
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']


Ваш класс работает должным образом. Он преобразует все входные значения в строки на лету. Это круто, не так ли? Когда вы создаете новый экземпляр StringList, инициализатор класса выполняет преобразование.

Когда вы добавляете, вставляете, расширяете или присваиваете новые значения экземплярам класса, методы, поддерживающие каждую операцию, будут выполнять процесс преобразования строк. Таким образом, элементы вашего списка всегда будут храниться в виде объектов string.

Подкласс UserList Из collections

Другим способом создания пользовательского класса, подобного списку, является использование класса UserList из модуля collections. Этот класс является оболочкой для встроенного типа list. Он был разработан для создания объектов, похожих на списки, когда еще не было возможности напрямую наследовать от встроенного класса list.

Несмотря на то, что потребность в этом классе была частично устранена возможностью прямого создания подклассов встроенного класса list, UserList по-прежнему доступен в стандартной библиотеке , как для удобства, так и для обратной совместимости.

Отличительной особенностью UserList является то, что он предоставляет вам доступ к своему атрибуту .data, который может облегчить создание пользовательских списков, поскольку вам не нужно использовать super() все время. Атрибут .data содержит обычный код Python list, который по умолчанию пуст.

Вот как вы можете переопределить свой класс StringList, унаследовав от UserList:

# string_list.py

from collections import UserList

class StringList(UserList):
    def __init__(self, iterable):
        super().__init__(str(item) for item in iterable)

    def __setitem__(self, index, item):
        self.data[index] = str(item)

    def insert(self, index, item):
        self.data.insert(index, str(item))

    def append(self, item):
        self.data.append(str(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            self.data.extend(other)
        else:
            self.data.extend(str(item) for item in other)


В этом примере доступ к атрибуту .data позволяет вам более простым образом закодировать класс, используя делегирование, что означает, что список в .data заботится об обработке всех запросов.

Теперь вам почти не нужно использовать продвинутые инструменты, такие как super(). Вам просто нужно вызвать эту функцию в инициализаторе класса, чтобы предотвратить проблемы в дальнейших сценариях наследования. В остальных методах вы просто используете .data, который содержит обычный список Python. Работа со списками - это навык, которым вы, вероятно, уже обладаете.

Примечание: В приведенном выше примере все будет в порядке, если вы повторно используете исходную внутреннюю реализацию StringList из предыдущего раздела но измените родительский класс с list на UserList. Ваш код будет работать так же. Однако использование .data может облегчить процесс кодирования классов, подобных спискам.

Эта новая версия работает так же, как и ваша первая версия StringList. Запустите следующий код, чтобы опробовать ее:

>>> from string_list import StringList

>>> data = StringList([1, 2, 2, 4, 5])
>>> data
['1', '2', '2', '4', '5']

>>> data.append(6)
>>> data
['1', '2', '2', '4', '5', '6']

>>> data.insert(0, 0)
>>> data
['0', '1', '2', '2', '4', '5', '6']

>>> data.extend([7, 8, 9])
>>> data
['0', '1', '2', '2', '4', '5', '6', '7', '8', '9']

>>> data[3] = 3
>>> data
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']


Как вы уже поняли, 

Отображение .data является наиболее важной функцией UserList. Этот атрибут может упростить ваши занятия, поскольку вам не нужно постоянно использовать super(). Вы можете просто воспользоваться преимуществами .data и использовать знакомый интерфейс list для работы с этим атрибутом.

Классы, похожие на списки кодирования: практические примеры

Вы уже знаете, как использовать list и UserList, когда вам нужно создать пользовательские классы, похожие на списки, которые добавляют или изменяют стандартную функциональность list.

По общему признанию, когда вы думаете о создании класса, похожего на список, наследование от list, вероятно, кажется более естественным, чем наследование от UserList, потому что разработчики Python знают о list. Они могут и не подозревать о существовании UserList.

Вы также знаете, что основное различие между этими двумя классами заключается в том, что при наследовании от UserList у вас есть доступ к атрибуту .data, который представляет собой обычный список, которым вы можете манипулировать с помощью стандартного list интерфейс. Напротив, наследование от list требует углубленных знаний о модели данных Python, включая такие инструменты, как встроенная функция super() и некоторые специальные методы.

В следующих разделах вы создадите несколько практических примеров, используя оба класса. После написания этих примеров вы будете лучше подготовлены к выбору подходящего инструмента для использования, когда вам нужно будет определить пользовательские классы, подобные спискам, в вашем коде.

Список, Который Принимает только Числовые Данные

В качестве первого примера создания класса, подобного списку, с пользовательским поведением, предположим, что вам нужен список, который принимает только числовые данные. Ваш список должен содержать только целое число, с плавающей точкой и сложные числа. Если вы попытаетесь сохранить значение любого другого типа данных, например строку, то ваш список должен вызвать TypeError.

Вот реализация класса NumberList с желаемой функциональностью:

# number_list.py

class NumberList(list):
    def __init__(self, iterable):
        super().__init__(self._validate_number(item) for item in iterable)

    def __setitem__(self, index, item):
        super().__setitem__(index, self._validate_number(item))

    def insert(self, index, item):
        super().insert(index, self._validate_number(item))

    def append(self, item):
        super().append(self._validate_number(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            super().extend(other)
        else:
            super().extend(self._validate_number(item) for item in other)

    def _validate_number(self, value):
        if isinstance(value, (int, float, complex)):
            return value
        raise TypeError(
            f"numeric value expected, got {type(value).__name__}"
        )


В этом примере ваш класс NumberList наследуется непосредственно от list. Это означает, что ваш класс использует все основные функциональные возможности совместно со встроенным классом list. Вы можете перебирать экземпляры NumberList, получать доступ к его элементам и обновлять их, используя их индексы, вызывать общие методы list и многое другое.

Теперь, чтобы убедиться, что каждый введенный элемент является числом, вам необходимо проверить каждый элемент во всех методах, которые поддерживают операции по добавлению новых элементов или обновлению существующих элементов в списке. Требуемые методы такие же, как в примере StringList, приведенном в разделе Наследование от встроенного в Python класса list.

Для проверки входных данных используется вспомогательный метод, называемый ._validate_number(). Этот метод использует встроенную функцию isinstance(), чтобы проверить, является ли текущее входное значение экземпляром int, float, или complex,, которые являются встроенными классами, представляющими числовые значения в Python.

Примечание: Более общим способом проверить, является ли значение числом в Python, было бы использование Number из модуля numbers. Это позволит вам также проверять объекты Fraction и Decimal.

Если входное значение является экземпляром числового типа данных, то ваша вспомогательная функция возвращает само значение. В противном случае функция генерирует TypeError исключение с соответствующим сообщением об ошибке.

Чтобы использовать NumberList, вернитесь к своей интерактивной сессии и запустите следующий код:

>>> from number_list import NumberList

>>> numbers = NumberList([1.1, 2, 3j])
>>> numbers
[1.1, 2, 3j]

>>> numbers.append("4.2")
Traceback (most recent call last):
    ...
TypeError: numeric value expected, got str

>>> numbers.append(4.2)
>>> numbers
[1.1, 2, 3j, 4.2]

>>> numbers.insert(0, "0")
Traceback (most recent call last):
    ...
TypeError: numeric value expected, got str

>>> numbers.insert(0, 0)
>>> numbers
[0, 1.1, 2, 3j, 4.2]

>>> numbers.extend(["5.3", "6"])
Traceback (most recent call last):
    ...
TypeError: numeric value expected, got str

>>> numbers.extend([5.3, 6])
>>> numbers
[0, 1.1, 2, 3j, 4.2, 5.3, 6]


В этих примерах операции, которые добавляют или изменяют данные в numbers, автоматически проверяют вводимые данные, чтобы гарантировать, что принимаются только числовые значения. Если вы добавите строковое значение к numbers, то получите TypeError.

Альтернативная реализация NumberList с использованием UserList может выглядеть примерно так:

# number_list.py

from collections import UserList

class NumberList(UserList):
    def __init__(self, iterable):
        super().__init__(self._validate_number(item) for item in iterable)

    def __setitem__(self, index, item):
        self.data[index] = self._validate_number(item)

    def insert(self, index, item):
        self.data.insert(index, self._validate_number(item))

    def append(self, item):
        self.data.append(self._validate_number(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            self.data.extend(other)
        else:
            self.data.extend(self._validate_number(item) for item in other)

    def _validate_number(self, value):
        if isinstance(value, (int, float, complex)):
            return value
        raise TypeError(
            f"numeric value expected, got {type(value).__name__}"
        )


В этой новой реализации NumberList вы наследуете от UserList. Опять же, ваш класс будет использовать все основные функциональные возможности обычного класса. list.

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

Обратите внимание, что в инициализаторе класса используется только super(), .__init__(). Это наилучшая практика при работе с наследованием в Python. Это позволяет вам правильно инициализировать атрибуты в родительском классе, ничего не нарушая.

Список С Дополнительными Функциями

Теперь предположим, что вам нужен класс, похожий на список, со всей стандартной функциональностью обычного Python list. Ваш класс также должен предоставлять некоторую дополнительную функциональность, заимствованную из типа данных Array в JavaScript. Например, вам понадобятся такие методы, как следующие:

  • .join() объединяет все элементы списка в одну строку.
  • .map(action) возвращает новые элементы, которые являются результатом применения вызываемого параметра action() к каждому элементу в базовом списке.
  • .filter(predicate) возвращает все элементы, которые возвращают True при вызове predicate() для них.
  • .for_each(func) вызывает func() для каждого элемента в базовом списке, чтобы создать некоторый побочный эффект.

Вот класс, который реализует все эти новые функции путем создания подклассов list:

# custom_list.py

class CustomList(list):
    def join(self, separator=" "):
        return separator.join(str(item) for item in self)

    def map(self, action):
        return type(self)(action(item) for item in self)

    def filter(self, predicate):
        return type(self)(item for item in self if predicate(item))

    def for_each(self, func):
        for item in self:
            func(item)


Метод .join() в CustomList принимает символ-разделитель в качестве аргумента и использует его для объединения элементов в текущем объекте списка, который представлен символом self. Для этого вы используете str.join() с генераторным выражением в качестве аргумента. Это генераторное выражение преобразует каждый элемент в строковый объект с помощью str().

Метод .map() возвращает объект CustomList. Чтобы создать этот объект, вы используете генераторное выражение, которое применяет action() к каждому элементу текущего объекта, self. Обратите внимание, что действие может быть любым вызываемым объектом, который принимает элемент в качестве аргумента и возвращает преобразованный элемент.

Метод .filter() также возвращает объект CustomList. Для создания этого объекта используется генераторное выражение, которое выдает элементы, для которых predicate() возвращает True. В этом случае predicate() должно быть логической функцией, которая возвращает True или False в зависимости от определенных условий, примененных к входному элементу.

Наконец, метод .for_each() вызывает func() для каждого элемента в базовом списке. Этот вызов ничего не возвращает, но вызывает некоторые побочные эффекты, как вы увидите ниже.

Чтобы использовать этот класс в своем коде, вы можете сделать что-то вроде следующего:

>>> from custom_list import CustomList

>>> words = CustomList(
...     [
...         "Hello,",
...         "Pythonista!",
...         "Welcome",
...         "to",
...         "Real",
...         "Python!"
...     ]
... )

>>> words.join()
'Hello, Pythonista! Welcome to Real Python!'

>>> words.map(str.upper)
['HELLO,', 'PYTHONISTA!', 'WELCOME', 'TO', 'REAL', 'PYTHON!']

>>> words.filter(lambda word: word.startswith("Py"))
['Pythonista!', 'Python!']

>>> words.for_each(print)
Hello,
Pythonista!
Welcome
to
Real
Python!


В этих примерах сначала вызывается .join() для words. Этот метод возвращает уникальную строку, которая получается в результате объединения всех элементов в базовом списке.

Вызов .map() возвращает объект CustomList, содержащий слова в верхнем регистре. Это преобразование является результатом применения str.upper() ко всем элементам в words. Этот метод работает практически аналогично встроенной функции map(). Основное отличие заключается в том, что вместо возврата списка встроенная функция map() возвращает итератор, который лениво возвращает преобразованные элементы .

Метод .filter() принимает в качестве аргумента функцию lambda. В примере эта функция lambda использует str.startswith() для выбора тех слов, которые начинаются с префикса "Py". Обратите внимание, что этот метод работает аналогично встроенной функции filter(), которая возвращает итератор вместо списка.

Наконец, вызов .for_each() на words выводит каждое слово на экран в качестве побочного эффекта вызова print() для каждого элемента в базовом списке. Обратите внимание, что функция, переданная в .for_each(), должна принимать элемент в качестве аргумента, но не должна возвращать никакого полезного значения.

Вы также можете реализовать CustomList, наследуя от UserList, а не от list. В этом случае вам не нужно изменять внутреннюю реализацию, только базовый класс:

# custom_list.py

from collections import UserList

class CustomList(UserList):
    def join(self, separator=" "):
        return separator.join(str(item) for item in self)

    def map(self, action):
        return type(self)(action(item) for item in self)

    def filter(self, predicate):
        return type(self)(item for item in self if predicate(item))

    def for_each(self, func):
        for item in self:
            func(item)


Обратите внимание, что в этом примере вы только что изменили родительский класс. Нет необходимости использовать .data напрямую. Однако вы можете использовать его, если хотите. Преимущество заключается в том, что вы предоставите больше контекста другим разработчикам, читающим ваш код:

# custom_list.py

from collections import UserList

class CustomList(UserList):
    def join(self, separator=" "):
        return separator.join(str(item) for item in self.data)

    def map(self, action):
        return type(self)(action(item) for item in self.data)

    def filter(self, predicate):
        return type(self)(item for item in self.data if predicate(item))

    def for_each(self, func):
        for item in self.data:
            func(item)


В этой новой версии CustomList() единственное изменение заключается в том, что вы заменили self на self.data, чтобы было понятно, что вы работаете с UserList подкласс. Это изменение делает ваш код более понятным.

С учетом производительности: list по сравнению UserList

На данный момент вы узнали, как создавать свои собственные классы, подобные спискам, наследуя их от list или UserList. Вы также знаете, что единственное видимое различие между этими двумя классами заключается в том, что UserList предоставляет атрибут .data, который может облегчить процесс кодирования.

В этом разделе вы рассмотрите аспект, который может оказаться важным при принятии решения о том, использовать ли list или UserList для создания пользовательских классов, подобных спискам. Это производительность!

Чтобы оценить, есть ли различия в производительности между классами, которые наследуются от list и UserList, вы будете использовать класс StringList. Продолжайте и создайте новый файл на Python, содержащий следующий код:

# performance.py

from collections import UserList

class StringList_list(list):
    def __init__(self, iterable):
        super().__init__(str(item) for item in iterable)

    def __setitem__(self, index, item):
        super().__setitem__(index, str(item))

    def insert(self, index, item):
        super().insert(index, str(item))

    def append(self, item):
        super().append(str(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            super().extend(other)
        else:
            super().extend(str(item) for item in other)

class StringList_UserList(UserList):
    def __init__(self, iterable):
        super().__init__(str(item) for item in iterable)

    def __setitem__(self, index, item):
        self.data[index] = str(item)

    def insert(self, index, item):
        self.data.insert(index, str(item))

    def append(self, item):
        self.data.append(str(item))

    def extend(self, other):
        if isinstance(other, type(self)):
            self.data.extend(other)
        else:
            self.data.extend(str(item) for item in other)


Эти два класса работают одинаково. Однако они внутренне различны. StringList_list наследуется от list, а его реализация основана на super(). В отличие от этого, StringList_UserList наследуется от UserList, и его реализация зависит от внутреннего атрибута .data.

Чтобы сравнить производительность этих двух классов, вам следует начать с определения времени выполнения стандартных операций со списком, таких как создание экземпляра. Однако в этих примерах оба инициализатора эквивалентны, поэтому они должны выполнять одинаковую работу.

Также полезно измерять время выполнения новых функций. Например, вы можете проверить время выполнения .extend(). Продолжайте и запустите следующий код:

>>> import timeit
>>> from performance import StringList_list, StringList_UserList
>>> init_data = range(10000)

>>> extended_list = StringList_list(init_data)
>>> list_extend = min(
...     timeit.repeat(
...         stmt="extended_list.extend(init_data)",
...         number=5,
...         repeat=2,
...         globals=globals(),
...     )
... ) * 1e6

>>> extended_user_list = StringList_UserList(init_data)
>>> user_list_extend = min(
...     timeit.repeat(
...         stmt="extended_user_list.extend(init_data)",
...         number=5,
...         repeat=2,
...         globals=globals(),
...     )
... ) * 1e6

>>> f"StringList_list().extend() time: {list_extend:.2f} μs"
'StringList_list().extend() time: 4632.08 μs'

>>> f"StringList_UserList().extend() time: {user_list_extend:.2f} μs"
'StringList_UserList().extend() time: 4612.62 μs'


В этом тесте производительности вы используете модуль timeit вместе с функцией min() для измерения времени выполнения фрагмента кода. Целевой код состоит из вызовов .extend() в экземплярах StringList_list и StringList_UserList с использованием некоторых примеров данных.

Разница в производительности между классом, основанным на list, и классом, основанным на UserList, в этом примере практически отсутствует.

Часто, когда вы создаете пользовательский класс, подобный списку, вы ожидаете, что подклассы list будут работать лучше, чем подклассы UserList. Почему? Потому что list написан на C и оптимизирован для повышения производительности, в то время как UserList - это класс-оболочка, написанный на чистом Python.

Однако в приведенном выше примере, похоже, это предположение не совсем верно. По этой причине, чтобы решить, какой суперкласс лучше всего подходит для вашего конкретного случая использования, обязательно запустите тест производительности.

Помимо производительности, наследование от list, возможно, является естественным способом в Python, главным образом потому, что list напрямую доступен разработчикам Python как встроенный класс. Кроме того, большинство разработчиков Python будут знакомы со списками и их стандартными функциями, что позволит им быстрее создавать классы, подобные спискам.

В отличие от этого, класс UserList находится в модуле collections, что означает, что вам придется импортировать его, если вы хотите использовать его в своем коде. Кроме того, не все разработчики Python знают о существовании UserList. Однако UserList все еще может быть полезным инструментом из-за удобства доступа к атрибуту .data, который может облегчить создание пользовательских классов, подобных спискам.

Заключение

Теперь вы узнали, как создавать пользовательские классы, похожие на списки с измененным и новым поведением. Чтобы сделать это, вы создали подкласс встроенного класса list напрямую. В качестве альтернативы, вы также унаследовали от класса UserList, который доступен в модуле collections.

Наследование от list и создание подклассов UserList являются подходящими стратегиями для решения проблемы создания ваших собственных спископодобных классов в Python.

В этом руководстве вы узнали, как:

  • Создавайте классы, подобные спискам, наследуя от встроенного list класса
  • Создавать классы, подобные спискам, путем создания подклассов UserList из модуля collections

Теперь вы лучше подготовлены к созданию собственных пользовательских списков, что позволит вам использовать всю мощь этого полезного и распространенного типа данных в Python.

Скачать бесплатно: Нажмите здесь, чтобы загрузить исходный код, который вы будете использовать для создания пользовательских классов, похожих на списки.

<статус завершения article-slug="inherit-python-list" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/статьи/наследуют-python-список/закладки/" data-api-article-завершение-статус-url="/api/v1/articles/inherit-python-list/completion_status/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0пользовательские списки на Python: наследование от списка пользователей" email-subject="Статья на Python для вас" twitter-text="Интересно #Python статья от @realpython:" url="https://realpython.com/inherit-python-list /" url-title="Пользовательские списки Python: наследование от list против UserList"> Back to Top