Python для начинающих: какая разница между tuple, list и set?
Язык программирования Python предоставляет четыре встроенных типа данных для хранения коллекций из объектов. Все они наделены различными свойствами и характеристиками: list (список), tuple (кортеж), set (множество) и dictionary (словарь).
В статье обсудим различия между списками, кортежами, множествами и словарями, а также поразмышляем, когда лучше использовать каждый из встроенных типов последовательностей Python.
Примечание: поскольку словарь связывает соответствующие значения с ключами, а это совсем другой сценарий использования по сравнению со списками, множествами и кортежами (которые просто содержат значения), словарь не участвует в обсуждении.
Зачем вообще выбирать?
По большей части любая из последовательностей Python применяется на практике без особых проблем, в любом приложении. Однако представьте, что перед вами поставлена легендарная задача найти иголку в стоге сена: какой тип данных Python эффективнее всего справится с такой задачей с точки зрения скорости и памяти?
Может стог сена — это список? Как насчет кортежа? Почему бы не использовать множества всегда? На какие предостережения следует обратить внимание?
Отличия между списком, кортежем и множеством
- Отличие 1: дубликаты.
Говоря проще, List и Tuple в Python как двойняшки разного пола, а тип данных Set для них как двоюродный брат. В отличие от списков или кортежей, множество не содержит дубликатов. Другими словами, элементы множества всегда уникальны. Получается, что множество удобно удаляет дубликаты, словно создано именно для этого.
set_example =
# fruit_set =
#
- Отличие 2: упорядоченность.
Наверняка вы слышали утверждение “множества и словари в Python не упорядочены”, но на сегодняшний день — это лишь половина правды в зависимости от того, какой версией Python вы пользуетесь. До Python версии 3.6 словари и множества действительно не сохраняли порядок элементов, но начиная с Python 3.7, dictionary и set официально упорядочены по времени добавления элементов. А вот list и tuple — это всегда упорядоченные последовательности объектов.
# Пример тогда ещё неупорядоченного множества в Python версии 3.5fruit_size = <>
>>> fruit_size[''] = 12
>>> fruit_size[''] = 16
>>> fruit_size[''] = 20
>>> fruit_size
- Отличие 3: индексация.
Что списки, что кортежи — оба поддерживают индексацию и срезы, а вот множества — нет.
fruit_list = ['', '', '']
fruit_list[1]
# ''animal_tuple = ('', '', '')
animal_tuple[2]
# ''vehicle_set =
vehicle_set[0]
# TypeError: 'set' object is not subscriptable
Когда выбирать список, а когда — кортеж?
Как упоминалось в руководстве ранее, кортеж — неизменяемый тип данных, тогда как список — изменяемый. Кроме того, размер кортежа фиксированный, а вот размер списка — динамический.
a_tuple = tuple(range(1000))
a_list = list(range(1000))a_tuple.__sizeof__() # 8024 байта
a_list.__sizeof__() # 9088 байт
- Список подходит, если:
- Последовательность планируется изменять.
- Планируется постепенно добавлять новые элементы в последовательность или удалять старые.
- Кортеж подходит, если:
- Последовательность НЕ планируется изменять.
- Все, что нужно от последовательности — это возможность поочередно перебирать постоянный набор элементов.
- Нужна последовательность элементов для ее назначения в качестве ключа словаря. Поскольку списки — это изменяемый тип данных, их нельзя применять в качестве ключей словаря.
- Важна скорость выполнения операций с последовательностью: из-за отсутствия возможности изменения, кортежи работают куда быстрее списков.
Когда выбирать множества?
Базовая структура типа данных “множество” — это хеш-таблица (Hash Table). Поэтому множества очень быстро справляются с проверкой элементов на вхождение, например содержится ли объект x в последовательности a_set .
Идея заключается в том, что поиск элемента в хэш-таблице — это операция O(1), то есть операция с постоянным временем выполнения.
Получается, всегда надо использовать множество?
По сути, если не нужно хранить дубликаты, то множество будет лучшим выбором, чем список.
Выводы
“Преждевременная оптимизация — корень всех зол”.
Итак, самое главное, что вам стоит запомнить по поводу списков, кортежей и множеств.
- Если необходимо хранить дубликаты, то выбирайте список или кортеж.
- Если НЕ планируется изменять последовательность после ее создания, то выбирайте кортеж, а не список.
- Если НЕ нужно хранить дубликаты, то воспользуйтесь множеством, так как они значительно быстрее определяют наличие объекта в последовательности.
В конечном итоге, по большей части не стоит слишком сильно задумываться о том, какого же типа данных последовательностью воспользоваться.
Главное — помнить о похожих чертах и особенностях встроенных типов данных Python.
- Скрейпинг PDF с нуля на Python: библиотеки tabula-py и Pandas
- Как вычислить миллионное число Фибоначчи на Python
- 3 важных рекомендации Django-программистам
Чем отличаются list, tuple и set? Зачем они нужны?
List (список), tuple (кортеж), set (множество) — это встроенные структуры данных языка python. Каждая из них имеет свои возможности и ограничения. Это позволяет выбрать наиболее подходящий способ хранения информации в программе.
List (список)
Базовая структура данных в python. Элементы в списке хранятся последовательно, каждому из них присвоены индексы, начиная с нуля. В отличие от массива, список может хранить объекты любого типа.
Создание списка
>>> my_list = [] # Создание пустого списка с помощью литерала списка >>> my_list = list() # Создание пустого списка с помощью встроенной функции >>> >>> my_list = [1,2,['a','b'],4,5] # Инициализация списка >>> >>> my_list = list('hello world') # Создание списка из итерируемого объекта >>> my_list ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'] >>> >>> my_list = [x for x in range(10)] # Генератор списков в действии >>> my_list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Доступные методы
- my_list.append(x) — добавляет x в конец списка
- my_list.clear() — очищает список
- my_list.copy() — возвращает копию списка my_list
- my_list.count(x) — возвращает кол-во элементов со значением x
- my_list.extend(x) — добавляет элементы списка x к концу списка my_list
- my_list.index(x,start,end) — возвращает индекс первого найденного x, можно задать промежуток для поиска (опционально)
- my_list.insert(index, x) — вставляет x на заданную позицию
- my_list.pop(index) — возвращает элемент с указанным индексом и удаляет его, если индекс не указан — возвращается и удаляется последний элемент
- my_list.remove(x) — удаляет первый элемент со значением x
- my_list.reverse() — инвертирует порядок элементов в списке
- my_list.sort(key=x) сортирует список на основе функции x
В каких случаях использовать?
Для хранения элементов, объединенных каким либо признаком. В случае, если изменение элементов и/или расширение списка не предполагается, следует использовать неизменяемый аналог — кортеж.
Tuple (кортёж)
Кортеж — это неизменяемый и более быстрый аналог списка. Он защищает хранимые данные от непреднамеренных изменений и может использоваться в качестве ключа в словарях (словарь — ассоциативный массив в python).
Создание кортежа.
>>> my_tuple = () # Создание кортежа с помощью литерала >>> my_tuple = tuple() # Создание кортежа с помощью встроенной функции >>> >>> my_tuple = (1,2,['a','b'],4,5) # Инициализация кортежа >>> >>> my_tuple = tuple('hello world') # Создание кортежа из итерируемого объекта >>> my_tuple ('h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd') >>> >>> my_tuple = tuple(2**x for x in [0, 1, 2, 3]) # Генератор кортежей >>> my_tuple (1, 2, 4, 8)
Доступные методы
- my_tuple.count(x) — возвращает кол-во элементов со значением x
- my_tuple.index(x,start,end) — возвращает индекс первого найденного x, можно задать промежуток для поиска (опционально)
В каких случаях использовать?
Для хранения данных вместо списка (если они не предполагают изменений).
Set (множество)
Множество — это набор уникальных элементов в случайном порядке (неупорядоченный список). Множества примечательны тем, что операция проверки “принадлежит ли объект множеству” происходит значительно быстрее аналогичных операций в других структурах данных.
Создание множества
>>> my_something = > # . Попытка создать множество при помощи литерала даст нам словарь >>> type(my_something) class 'dict'> >>> >>> my_set = set() # Создание при помощи встроенной функции >>> >>> my_set = 1,2,3,4,5> # Инициализация множества >>> >>> my_set = set('hello world') # Создания множества из итерируемого объекта >>> my_set 'r', 'o', 'e', 'h', 'd', 'w', 'l', ' '> >>> >>> my_set = x for x in range(10)> # Генератор множеств >>> my_set 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>
Доступные методы
- my_set.add(x) — добавляет x во множество
- my_set.difference(x) — возвращает множество элементов my_set, которые не входят во множество x
- my_set.difference_update(x) — удаляет из множества my_set все элементы, которые входят во множество x
- my_set.discard(x) — удаляет элемент x из my_set
- my_set.intersection(x) — возвращает элементы общие для множеств my_set и x
- my_set.intersection_update(x) — удаляет из множества my_set элементы, которых нет во множестве x
- my_set.isdisjoint(x) — возвращает true если my_set и x не содержат одинаковых значений
- my_set.issubset(x) — возвращает true если все элементы my_set входят во множество x
- my_set.issuperset(x) — возвращает true если все элементы x входят во множество my_set
- my_set.pop() — возвращает и удаляет первый (на данный момент) элемент множества
- my_set.remove(x) — удаляет x из множества
- my_set.symmetric_difference(x) — возвращает все элементы из x и my_set, которые встречаются только в одном из множеств
- my_set.symmetric_difference_update(x) — обновляет исходное множество таким образом, что оно будет состоять из всех элементов x и my_set, которые встречаются только в одном из множеств
- my_set.union(x) — возвращает новое множество, состоящее из всех элементов x и my_set
- my_set.update(x) — добавляет в my_set все элементы x
В каких случаях использовать?
Когда необходимо проверять принадлежит ли значение набору уникальных элементов и отсутствует необходимость поддерживать порядок в данном наборе.
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Списки (List) vs Кортежи (Typle)
Списки и кортежи используются для последовательного хранения одного или нескольких объектов или типов данных Python. И те и другие могут хранить любые данные, такие как целые числа, плавающие числа, строки и словари. Списки и кортежи похожи по большинству параметров, но здесь мы опишем основные различия между ними.
Различия в представлении
Представление списков и кортежей отличается незначительно. Списки обычно заключаются в квадратную скобку ё [] , а элементы — через запятую. Кортежи заключаются в круглую скобку () , а элементы разделяются запятой. Скобки использовать необязательно, и такие типы кортежей называются упаковкой кортежей.
Рассмотрим следующий пример.
list1 = ['JavaTpoint', 1, 2, 54.30, ] print(type(list)) tuple1 = ('JavaTpoint',5,8,31.9,[1,2,3]) print(type(tuple1))
В приведенной выше программе мы определили переменную list1 , которая содержит список данных разного типа с индексом от 0 до 4 . Мы определили еще одну переменную tuple1 , которая содержит кортеж данных разного типа. Она заключена в рамку () .
Изменяемые списки и неизменяемые кортежи в Python
Это самое важное различие между списком и кортежем, в то время как списки являются изменяемыми, а кортежи — неизменяемыми. Списки являются изменяемыми, что означает, что объект Python может быть изменен после создания, в то время как кортежи не могут быть изменены после создания. Рассмотрим приведенный пример.
a = ["Peter","Joseph","Mathew","Ricky"] print(a)
['Peter', 'Joseph', 'Mathew', 'Ricky']
Теперь мы меняем 0-й элемент индекса «Peter» на «Samson».
a[0] = "Samson" print(a)
['Samson', 'Joseph', 'Mathew', 'Ricky']
Теперь мы создаем кортеж и делаем то же самое.
a = (10,20,"JavaTpoint",30,40) print(a)
(10, 20, 'JavaTpoint', 30, 40)
a[0] = 50
TypeError Traceback (most recent call last) in ----> 1 a[0] = 50 TypeError: 'tuple' object does not support item assignment
Мы получаем ошибку при изменении 1-го элемента кортежа из-за неизменяемости. Он не поддерживает присвоение элементов.
Отладка кортежей и списков в Python
Кортежи легко отлаживать в большом проекте из-за их неизменяемости. Если у нас небольшой проект или меньшее количество данных, то списки играют эффективную роль. Рассмотрим следующий пример:
a = [6,9,4,3,7,0,1] # Copying address of a in b b = a a[3] = "JavaToint" print(a)
[6, 9, 4, 'JavaToint', 7, 0, 1]
В приведенном выше коде мы сделали b = a ; здесь мы не копируем объект списка из b в a . b ссылается на адрес списка a . Это означает, что если мы сделаем изменение в b , то это отразится так же, как и в списке a , и это облегчает отладку. Но это сложно для серьезного проекта, где объекты Python могут иметь множество ссылок.
Будет сложно отслеживать эти изменения в списках, но неизменяемый объект кортежа не может измениться после создания. Поэтому кортежи легче отлаживать.
Поддержка функций у кортежей и списков в Python
Кортежи поддерживают меньше операций, чем списки. Встроенный dir(object) используется для получения всех поддерживаемых функций для списка и кортежа.
Функциии списков (List)
dir(list)
['__add__','__class__','__contains__','__delattr__','__delitem__','__dir_, '__doc__','__eq__','__format__', '__get__','__getattribute__','__getitem_' '__gt__','__hash__','__iadd__','__imul__','__init__','__init_subclass__''__iter__','__le__','__len__','__lt__','__mul__', '__ne__','__new__', '__reduce__', '__reduce_ex__','__repr__','__reversed__','__rmul__','__setattr__','__setitem__','__sizeof__','__str__','__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Функциии кортежей (Tuple)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
Расходование памяти у списков (List) и у кортежей (Tuple) в Python
Кортежи более эффективны с точки зрения использования памяти, чем списки, поскольку кортеж имеет меньше встроенных операций. Списки подходят для работы с небольшим количеством элементов, в то время как кортежи немного быстрее, чем списки, для работы с огромным количеством данных.
Tuple = (1,2,3,4,5,6,7,8,9,0,5485,87525,955,3343,53234,6423,623456,234535) List = [1,2,3,4,5,6,7,8,9,0,78,34,43,32,43,55,54,212,642,533,43434,54532 ] print('Tuple size =', Tuple.__sizeof__()) # Tuple size = 52 print('List size =', List.__sizeof__())
Tuple size = 168 List size = 216
В некоторых случаях списки могут показаться более полезными, чем кортежи. Но кортежи являются важными структурами данных в Python. Кортежи обычно используются для неизменяемых данных, или можно сказать, что данные в кортежах «защищены от записи». Кортежи передают интерпретатору Python указание на то, что данные не должны меняться в будущем. Мы можем использовать кортеж так же, как и словарь, не используя ключи для хранения данных, например:
list1 = [(101, "Mike", 24),(102, 'Hussey', 26),(103, 'David', 27),(104, 'Warner', 29)]
Кортежи могут использовать ключи словаря, поскольку они хэшируемы и неизменяемы, в то время как списки не могут использовать ключи словаря.
dict = #valid dictionary dict = #Invalid dictionary
Оптимизации, используемые в Python: список и кортеж
В Python, есть два похожих типа — список (list) и кортеж (tuple). Самая известная разница между ними состоит в том, что кортежи неизменяемы.
Вы не можете изменить объекты в tuple:
>>> a = (1,2,3) >>> a[0] = 10 Traceback (most recent call last): File "", line 1, in TypeError: 'tuple' object does not support item assignment
Но вы можете модифицировать изменяемые объекты внутри кортежа:
>>> b = (1,[1,2,3],3) >>> b[1] [1, 2, 3] >>> b[1].append(4) >>> b (1, [1, 2, 3, 4], 3)
Внутри CPython (стандартного интерпретатора), список и кортеж реализованы как лист из указателей (ссылок) на Python объекты, т.е. физически они не хранят объекты рядом с друг другом. Когда вы удаляете объект из списка происходит удаление ссылки на этот объект. Если на объект ещё кто-то ссылается, то он продолжит находиться в памяти.
Кортежи
Несмотря на тот факт, что кортежи намного реже встречаются в коде и не так популярны, это очень фундаментальный тип, который Python постоянно использует для внутренних целей.
Вы можете не замечать, но вы используете кортежи когда:
- работаете с аргументами или параметрами (они хранятся как кортежи)
- возвращаете две или более переменных из функции
- итерируете ключи-значения в словаре
- используете форматирование строк
>>> import gc >>> def type_stats(type_obj): . count = 0 . for obj in gc.get_objects(): . if type(obj) == type_obj: . count += 1 . return count . >>> type_stats(tuple) 3136 >>> type_stats(list) 659 >>> import pandas >>> type_stats(tuple) 6953 >>> type_stats(list) 2455
Пустые списки vs пустые кортежи
Пустой кортеж работает как синглтон, т.е. в памяти запущенного Python скрипта всегда находится только один пустой кортеж. Все пустые кортежи просто ссылаются на один и тот же объект, это возможно благодаря тому, что кортежи неизменяемы. Такой подход сохраняет много памяти и ускоряет процесс работы с пустыми кортежами.
>>> a = () >>> b = () >>> a is b True >>> id(a) 4409020488 >>> id(b) 4409020488 >>> # В CPython, функция id возвращает адрес в памяти.
Но это не работает со списками, ведь они могут быть изменены:
>>> a = [] >>> b = [] >>> a is b False >>> id(a) 4465566920 >>> id(b) 4465370632
Оптимизация выделения памяти для кортежей
Для того, чтобы снизить фрагментацию памяти и ускорить создание кортежей, Python переиспользует старые кортежи, которые были удалены. Если кортеж состоит из менее чем 20 элементов и больше не используется, то вместо удаления Python помещает его в специальный список, в котором хранятся свободные для повторного использования кортежи.
Этот список разделен на 20 групп, где каждая группа представляет из себя список кортежей размера n, где n от 0 до 20. Каждая группа может хранить до 2 000 свободных кортежей. Первая группа хранит только один элемент и представляет из себя список из одного пустого кортежа.
>>> a = (1,2,3) >>> id(a) 4427578104 >>> del a >>> b = (1,2,4) >>> id(b) 4427578104
В примере выше, мы можем видеть, что a и b имеют одинаковый адрес в памяти. Это происходит из-за того, что мы мгновенно заняли свободный кортеж такого же размера.
Оптимизация выделения памяти для списков
Так как списки могут изменяться, такую же оптимизацию как в случае с кортежами провернуть уже не получится. Несмотря на это, для списков используется похожая оптимизация нацеленная на пустые списки. Если пустой список удаляется, то он так же может быть переиспользован в дальнейшем.
>>> a = [] >>> id(a) 4465566792 >>> del a >>> b = [] >>> id(b) 4465566792
Изменение размера списка
Чтобы избежать накладные расходы на постоянное изменение размера списков, Python не изменяет его размер каждый раз, как только это требуется. Вместо этого, в каждом списке есть набор дополнительных ячеек, которые скрыты для пользователя, но в дальнейшем могут быть использованы для новых элементов. Как только скрытые ячейки заканчиваются, Python добавляет дополнительное место под новые элементы. Причём делает это с хорошим запасом, количество скрытых ячеек выбирается на основе текущего размера списка — чем он больше, тем больше дополнительных скрытых слотов под новые элементы.
Эта оптимизация особенно выручает, когда вы пытайтесь добавлять множество элементов в цикле.
Паттерн роста размера списка выглядит примерно так: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88,…
Для примера, если вы хотите добавить новый элемент в список с 8 элементами, то свободных ячеек в нём уже не будет и Python сразу расширит его размер до 16 ячеек, где 9 из них будут заняты и видны пользователю.
Формула выбора размера написанная на Python:
>>> def get_new_size(n_items): . new_size = n_items + (n_items // 2 ** 3) . if n_items < 9: . new_size += 3 . else: . new_size += 6 . . return new_size . >>> get_new_size(9) 16
Скорость
Если сравнивать эти два типа по скорости, то в среднем по больнице, кортежи слегка быстрее списков. У Raymond Hettinger есть отличное объяснение разницы в скорости на stackoverflow.
P.S.: Я являюсь автором этой статьи, можете задавать любые вопросы.