An open API service indexing awesome lists of open source software.

https://github.com/yuldashov10/topic_11

11. Работа с кортежами
https://github.com/yuldashov10/topic_11

learning-python programming python python3 shox-py

Last synced: 7 months ago
JSON representation

11. Работа с кортежами

Awesome Lists containing this project

README

          

# 11. Работа с кортежами

![cover.svg](img/cover.svg)

---

## Вступление

Добро пожаловать в тему кортежей!

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

---

## Содержание

- [Что такое кортежи](#что-такое-кортежи)
- [Как создавать и обращаться к кортежам](#как-создавать-и-обращаться-к-кортежам)
- [В чем отличие от списков](#в-чем-отличие-от-списков)
- [Какие операции и методы можно применять к кортежам](#какие-операции-и-методы-можно-применять-к-кортежам)
- [Где они реально применяются](#где-они-реально-применяются)
- [Аннотации типов](#аннотации-типов)
- [Задания](#задания)

---

## Что такое кортежи

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

- _читать, изменять, добавлять, удалять и заменять элементы_.

С кортежами всё строже, они **неизменяемы**. Это значит, что после создания кортежа
мы не можем изменить его содержимое:

- _ни добавить, ни удалить, ни заменить элемент_.

То есть операции, которые были привычны для списков, вроде `.remove()`, `.insert()`, `.pop()`, `.append()`,
в случае с кортежами просто не работают. У кортежей этих методов нет вовсе.

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

---

## Как создавать и обращаться к кортежам

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

Рассмотрим все эти варианты на примерах.

**Создание пустого кортежа:**

* С помощью круглых скобок

```python
empty: tuple = ()
print(empty) # ()
```

* С использованием функции `tuple()`

```python
empty_2: tuple = tuple()
print(empty_2) # ()
```

**Создание кортежа с данными:**

* С помощью круглых скобок

```python
nums: tuple[int | float, ...] = (3.1415, 12, 25, 2.78, 0.000343)
```

* С использованием функции `tuple()`

```python
chars: tuple[str, ...] = tuple("abc")
print(chars) # ('a', 'b', 'c')
```

```python
odds: tuple[int, ...] = tuple(range(1, 18, 2)) # кортеж нечетных чисел от 1 до 17 включительно
print(odds) # (1, 3, 5, 7, 9, 11, 13, 15, 17)
```

```python
nums: list[int] = [0, -3, 10, 1, 1, -9, 10, -14, -13, 12]

nums.sort()
print(nums, type(nums)) # [-14, -13, -9, -3, 0, 1, 1, 10, 10, 12]

sorted_nums: tuple[int, ...] = tuple(nums)
print(sorted_nums, type(sorted_nums)) # (-14, -13, -9, -3, 0, 1, 1, 10, 10, 12)
```

* Создание кортежа с одним элементом

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

```python
its_wrong: str = ("Write once, run anywhere")
print(its_wrong, type(its_wrong)) # Write once, run anywhere

its_right: tuple[str] = ("The Zen of Python",)
print(its_right, type(its_right)) # ('The Zen of Python',)
```

* Обращение к элементам кортежа

Обращение к элементам кортежа работает точно так же, как и со списками:

- по индексу
- с использованием срезов

```python
fruits: tuple[str, ...] = ("яблоко", "банан", "груша", "айва", "абрикос",)

print(fruits[2]) # груша
print(fruits[-2]) # айва
print(fruits[1:5]) # ('банан', 'груша', 'айва', 'абрикос')

fruits[3] = "киви" # TypeError: 'tuple' object does not support item assignment
```

Как видно, в плане обращения к элементам кортеж ничем не отличается от списка. Основное отличие проявляется,
когда мы хотим что-то изменить, и тут кортеж говорит строгое **"нельзя"**.

---

## В чем отличие от списков

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

- **Список** - изменяемый (`mutable`) - Можно добавлять, удалять, заменять элементы.
- **Кортеж** - неизменяемый (`immutable`) - После создания ничего менять нельзя.

**Почему это важно?**

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

```python
months_unsafe: list[str] = [
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"
]

months_safe: tuple[str, ...] = (
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"
)

months_unsafe[2] = "Март 03"
print(
months_unsafe) # ['Январь', 'Февраль', 'Март 03', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']

months_safe[4] = "Май 05" # TypeError: 'tuple' object does not support item assignment
print(months_safe)
```

**Сравнение по памяти**

Чтобы узнать, сколько памяти занимает список и кортеж, можно использовать атрибут `.__sizeof__()` или
через `sys.getsizeof()`:

```python
import sys

print(sys.getsizeof(months_unsafe)) # 152
print(sys.getsizeof(months_safe)) # 136

print(months_unsafe.__sizeof__()) # 136
print(months_safe.__sizeof__()) # 120
```

> - `__sizeof__()` показывает **чистый размер объекта в байтах**, без учёта вспомогательной информации интерпретатора.
> - `sys.getsizeof()` - обёртка над `__sizeof__()`, которая добавляет размер **служебной структуры Python**,
> обычно +16 байт. Поэтому `getsizeof()` почти всегда немного больше, чем `__sizeof__()`.
> - На практике лучше использовать `sys.getsizeof()`, особенно если нужно понять, сколько в итоге памяти всё это съест.

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

**Сравнение по скорости**

Из-за своей неизменяемости кортежи обрабатываются быстрее, особенно при передаче в функции и при создании.
Можем убедиться в этом, сравнив время выполнения:

```python
import timeit

print(
"Список:", timeit.timeit("['Январь', 'Февраль', 'Март']", number=1_000_000), "мс"
) # Список: 0.03748495801119134 мс

print(
"Кортеж:", timeit.timeit("('Январь', 'Февраль', 'Март')", number=1_000_000), "мс"
) # Кортеж: 0.005023542005801573 мс
```

Практика показывает, что кортеж создаётся быстрее. Опять же, причина - внутренняя оптимизация под "неизменяемость".

### Таблица 11.1: Сравнение списков и кортежей

| Критерий | Список | Кортеж |
|---------------------------------------------|-------------------------|--------------------------|
| Изменяемость | Да | Нет |
| Скорость создания | Медленнее | Быстрее |
| Используемая память | Больше | Меньше |
| Методы: `append`, `pop`, `remove`, `insert` | Есть | Нет |
| Подходит для: | Часто меняющихся данных | Статичных наборов данных |

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

---

## Какие операции и методы можно применять к кортежам

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

**Операции над кортежами**

Хотя кортежи и **неизменяемы**, с ними всё же можно выполнять ряд операций. Эти операции не изменяют кортеж,
а создают новый объект.

- Создание с повторением

```python
nums: tuple[int, ...] = (8,) * 5
print(nums) # (8, 8, 8, 8, 8)
```

- Сложение (объединение) кортежей

Можно "сложить" два кортежа - результатом будет новый кортеж, содержащий элементы обоих.

```python
fruits: tuple[str, ...] = ("яблоко", "банан", "груша", "айва", "абрикос",)
berries: tuple[str, ...] = ("клубника", "ежевика", "малина", "виноград", "арбуз")

print(id(fruits), id(berries)) # 4368944400 4368946880

mix: tuple[str, ...] = fruits + berries
print(mix)
# ('яблоко', 'банан', 'груша', 'айва', 'абрикос', 'клубника', 'ежевика', 'малина', 'виноград', 'арбуз')
print(id(mix)) # 4368409536
```

- Операторы `in` и `not in`

Кортежи поддерживают проверку на наличие элемента.

```python
fruits: tuple[str, ...] = ("яблоко", "банан", "груша", "айва", "абрикос",)

print("банан" in fruits) # True
print("ананас" not in fruits) # True
```

- Итерирование в цикле

Можно перебирать кортеж в `for`, как и список.

```python
fruits: tuple[str, ...] = ("яблоко", "банан", "груша", "айва", "абрикос",)

for fruit in fruits:
print(fruit)
```

**Методы кортежей**

У кортежей всего два метода, они не меняют кортеж, а просто возвращают информацию.

`tuple.count(x)` - считает, сколько раз элемент `x` встречается в кортеже.

```python
names: tuple[str, ...] = ("Алиса", "Петя", "Вася", "Алиса", "Толя", "Ваня", "Алиса", "Саша", "Миша")

print(names.count("Алиса")) # 3
```

---

`tuple.index(x)` - возвращает индекс первого вхождения элемента `x`.

```python
names: tuple[str, ...] = ("Алиса", "Петя", "Вася", "Алиса", "Толя", "Ваня", "Алиса", "Саша", "Миша")

print(names.index("Алиса")) # 0
print(names.index("Толя")) # 4
```

Если элемент не найден - будет ошибка `ValueError`:

```python
names: tuple[str, ...] = ("Алиса", "Петя", "Вася", "Алиса", "Толя", "Ваня", "Алиса", "Саша", "Миша")

print(names.index("Макс")) # ValueError: tuple.index(x): x not in tuple
```

### Таблица 11.2: Основные операции и методы кортежей

| Операция / Метод | Описание |
|------------------|-----------------------------------------------|
| `in`, `not in` | Проверка наличия элемента в кортеже |
| `for x in tuple` | Перебор элементов |
| `tuple + tuple` | Сложение кортежей |
| `tuple * n` | Повторение кортежа `n` раз |
| `tuple.count(x)` | Сколько раз элемент `x` встречается в кортеже |
| `tuple.index(x)` | Индекс первого вхождения элемента `x` |

Если вы работаете с постоянным набором данных, к которому не нужно вносить изменения - кортеж подойдёт идеально.

---

## Где они реально применяются

Кортежи часто встречаются в реальных проектах, особенно там, где важно, чтобы данные не менялись.
Вот несколько типичных случаев:

- Функции, возвращающие несколько значений

Если функция возвращает сразу несколько результатов, их удобно упаковать в кортеж:

```python
def get_user() -> tuple[str, int]:
return "Анна", 30

res: tuple[str, int] = get_user()
print(res) # ('Анна', 30)
```

- Координаты, размеры, цвета

Часто используются в графике, играх, математике:

```python
point: tuple[int, int] = (10, 20)
size: tuple[int, int] = (1920, 1080)
color: tuple[int, int, int] = (255, 255, 0)
```

- Хешируемые ключи в словарях

Кортеж - неизменяемый, а значит его можно использовать как ключ в `dict`:

```python
phone_book: dict[tuple[str, str], str] = {
("Иванов", "Иван"): "8-900-123-45-67",
("Петрова", "Мария"): "8-908-765-43-21",
}

print(phone_book[("Петрова", "Мария")]) # 8-908-765-43-21
```

> На данный момент эти примеры могут показаться сложными, пожалуйста, наберитесь терпения, мы скоро рассмотрим эти темы.

---

## Аннотации типов

Если вы ещё не используете аннотации типов, то **используйте** - это хорошая практика.

- Подход до Python 3.9

Для более старых версий Python необходимо импортировать `Tuple` из модуля `typing`:

```python
from typing import Tuple

person: Tuple[str, int] = ("Алиса", 30)
```

Это говорит о том, что кортеж содержит два значения: "строка" и "целое число".

- Подход начиная с Python 3.9

Начиная с версии **3.9** можно использовать встроенный синтаксис, без импорта:

```python
person: tuple[str, int] = ("Алиса", 30)
```

Это то же самое, что было в примере выше.

- Что означает `...` в аннотациях?

Если вы работаете с кортежем произвольной длины, где все элементы одного типа, можно использовать многоточие `...`:

```python
numbers: tuple[int, ...] = (1, 2, 3, 4, 5, 6)
```

> `tuple[int, ...]` означает: "кортеж, содержащий только `int`, любой длины".

```python
coords_2d: tuple[float, float] = (12.5, -3.8) # длина кортежа постоянная - 2.

names: tuple[str, ...] = ("Алиса", "Боб", "Чарли") # кортеж строк произвольной длины.

users: list[tuple[str, int]] = [
("Вася", 25),
("Петя", 31),
("Саша", 18),
] # список кортежей из двух элементов (строка, целое число) произвольной длины.
```

Такой подход полезен и для читаемости, и при работе со статическими анализаторами кода
[mypy](https://pypi.org/project/mypy/), [pyright](https://pypi.org/project/pyright/), и т.д.

---

## Заключение

Мы рассмотрели кортежи достаточно подробно, но это ещё не всё. Всегда найдётся что-то новенькое.
Например, мы не стали подробно останавливаться на **распаковке значений**, **вложенных структурах**
и особенностях работы с изменяемыми объектами внутри кортежей. Всё лучшее приходит с опытом - тема за темой
вы будете получать всё больше знаний о структурах. Ниже несколько примеров для размышления.

**Распаковка значений**:

```python
first_name, last_name, age = "Иван", "Иванов", 25 # да, это кортеж - круглые скобки не обязательны,
# но всё же, лучше указывать их явно: "Явное лучше, чем неявное"

print(first_name) # Иван
print(last_name) # Иванов
print(age) # 25
```

**Вложенные структуры**:

- Кортеж кортежей:

```python
users: tuple[tuple[str, int], ...] = (
("Вася", 25),
("Петя", 31),
("Саша", 18),
)
```

- Кортеж списков

> Важно!: сам кортеж изменить нельзя, но если он содержит **изменяемые объекты**, например, список,
> то такие вложенные элементы - изменяемы.

```python
users: tuple[list[str | int], ...] = (
["Вася", 25],
["Петя", 31],
["Саша", 18],
)

users[1][0] = "Толя"

print(users) # (['Вася', 25], ['Толя', 31], ['Саша', 18])

```

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

---

## [Задания](./tasks/TASKS.md)

Практические задания для самостоятельной работы.

- Постарайтесь использовать только те знания, которые были изучены в пройденных темах.
Не стоит использовать конструкции, которые мы ещё не разбирали.
- Убедитесь, что удалили все лишние комментарии из вашего кода.
- Постарайтесь указывать аннотации типов для переменных.
- Рекомендуем решить все задания самостоятельно.

Если возникнут трудности, не стесняйтесь обратиться за помощью в наш [Телеграм-чат](https://t.me/shox_py_discuss).
Чтобы перейти к заданиям, кликните на заголовок **Задания** или нажмите [сюда](./tasks/TASKS.md).

---