📦 Репозиторий занятия 28

Урок 28. Списки. Работа с памятью

Как работать с репозиторием

Каждая карточка говорит, о чём файл, что он выводит и что в нём искать. Код виден прямо здесь: его можно скопировать одной кнопкой, скачать файл или открыть его целиком.

Маршрут изучения

  1. Прочитайте описание: по нему уже понятно, о чём файл и что он выведет.
  2. Предскажите вывод: сравните своё предположение со строкой «Что выводит».
  3. Запустите: скачайте файл или скопируйте код кнопкой и выполните его у себя.
  4. Измените: поменяйте одно условие или значение и объясните новый результат.

Файлы: рекомендуемый порядок

1
MarkdownРазбор концепции78 строк

Устройство памяти процесса: стек, куча и сегменты данных

less_15__memory__deep_copy/theory_01__memory_organisation.md

Схема адресного пространства процесса — code segment, data segment, heap и stack — с указанием, в какую сторону растёт каждый сегмент. Ключевая мысль для Python-разработчика вынесена в отдельный блок: call stack (системный стек вызовов функций) и структура данных «стек» (list или deque в программе) — это два разных понятия, совпадающие только по имени; программный стек физически хранится в куче, как и все остальные объекты Python.

  • Схема памяти: kernel space, stack, heap, data segment, code segment
  • Heap растёт вверх — здесь живут все пользовательские объекты Python
  • Stack (call stack) растёт вниз — кадры вызовов функций
  • Различие call stack (системный) и list/deque как «стек» (в куче)
Показать начало файла (78 строк всего)
## Общая структура распределения памяти процесса

Обычно (в ОС Linux, Windows, macOS, и для Python в частности) процесс получает адресное пространство,  
которое делится на **сегменты**. Схематично это можно изобразить так:

```
Высокие адреса памяти
─────────────────────────────────────
|   Kernel space (ядро ОС)          |  ← процесс сюда напрямую не лезет
─────────────────────────────────────
|   Stack (стек вызовов)            |  ← растёт вниз (к меньшим адресам)
──────────────── ↕ ──────────────────
|                                   |
|   (свободная память)              |
|                                   |
──────────────── ↕ ──────────────────
|                                   |  ← растёт вверх (к большим адресам)
|   Heap (куча)                     |       в Python (в отличии от других языков программирования),
|                                   |       все объекты, созданные пользователем, живут именно здесь.
─────────────────────────────────────
|                                   |  ← глобальные и статические данные 
|   Data segment                    |       У Python-процесса здесь живут переменные 
|                                   |       самого интерпретатора
─────────────────────────────────────
|   Code segment (text segment)     |  ← машинный код программы
─────────────────────────────────────
Низкие адреса памяти
```

---

## Основные сегменты

1. **Code segment (текстовый сегмент)**

   * хранит скомпилированный код программы (в Python это логика интерпретатора и исполняемый байт-код).
   * обычно только для чтения.

2. **Data segment**

   * глобальные и статические переменные.
   * делится на `.data` (инициализированные) и `.bss` (неинициализированные).

3. **Heap (куча)**

   * выделяется динамически во время работы программы.
   * хранит объекты, которые создаются «на лету» (в Python это **все объекты: int, list, dict, class и т.д.**).
   * растёт **вверх** (к большим адресам).
   * управляется сборщиком мусора (GC) или вручную (в C).

4. **Stack (стек вызовов)**

   * хранит **кадры вызовов функций (stack frames)**.
   * в каждом кадре: локальные переменные, аргументы функции, адрес возврата.
   * растёт **вниз** (к меньшим адресам).
   * автоматически очищается при выходе из функции.

---

## Важное уточнение
…
Проверьте себя: Если структура данных 'стек' в Python реализована через list, то в каком сегменте памяти физически находятся её элементы — и почему не в call stack?
2
MarkdownРазбор концепции52 строк

Mutable и immutable типы: пять характеристик каждой группы

less_15__memory__deep_copy/theory_02__mutable__vs__immutable.md

Сопоставляет изменяемые (list, dict, set) и неизменяемые (int, float, str, tuple, frozenset, bytes) типы по пяти признакам сразу: возможность изменения на месте, ссылочная природа, эффективность, наличие мутирующих методов и поведение при передаче в функцию. Итоговая таблица сжимает всё в четыре строки для быстрого сравнения.

  • Mutable: append/extend/insert меняют объект без создания нового
  • Immutable: любая 'модификация' (replace, +, срез) создаёт новый объект
  • Ссылочная природа — переменные хранят ссылки в обоих случаях
  • Передача в функцию: mutable-изменения видны снаружи, immutable — нет
  • Итоговая таблица различий: модификация, видимость изменений, методы, примеры
Показать начало файла (52 строк всего)
# Изменяемые и неизменяемые типы данных

## Изменяемые (mutable) типы данных

### Примеры

```python
list, dict, set, . . .
```

### Характеристики

| Свойство                               | Описание                                                                                                                          |
| -------------------------------------- |-----------------------------------------------------------------------------------------------------------------------------------|
| **Изменение в месте (in-place)**       | Содержимое объекта можно модифицировать без создания нового объекта. <br>Например, добавление элемента в список через `append()`. |
| **Ссылочная природа**                  | Переменные и аргументы функций хранят ссылки на один объект. <br>Изменение через одну ссылку видно через все остальные.           |
| **Эффективность**                      | Меньше расход памяти и меньше операций копирования, <br>так как используется один объект.                                         |
| **Методы изменения**                   | `append()`, `extend()`, `insert()`, и т.д.                                                                                        |
| **Особенности при передаче в функцию** | Изменения внутри функции сохраняются вне её, <br>так как функция работает с той же ссылкой.                                       |

---

## Неизменяемые (immutable) типы данных

### Примеры

```python
int, float, bool, str, tuple, frozenset, bytes, . . .
```

### Характеристики

| Свойство                               | Описание                                                                                                                                                      |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Невозможность изменения в месте**    | Любая «модификация» создаёт новый объект, <br>старый остаётся неизменным.                                                                                         |
| **Ссылочная природа**                  | Передача объекта в функцию создаёт ссылку, <br>но любые «изменения» создадут новый объект, старый не изменится.                                                   |
| **Эффективность**                      | Безопасно для многопоточности; <br>меньше неожиданных побочных эффектов, <br>но возможны дополнительные затраты на создание объектов.                                 |
| **Методы изменения**                   | Не имеют методов для изменения содержимого. <br>Доступны только методы, возвращающие новый объект <br>(`replace()`, `upper()`, `+`, `*`, срезы для строк и кортежей). |
| **Особенности при передаче в функцию** | Изменения внутри функции не влияют на объект вне функции.                                                                                                     |

---

### Ключевые различия

| Признак             | Mutable                        | Immutable                                         |
| ------------------- |--------------------------------|---------------------------------------------------|
| Модификация объекта | Да, in-place                   | Нет, создаётся новый объект                       |
| Видимость изменений | Видно через все ссылки         | Нет, изменения локальны <br>(создан новый объект) |
| Методы              | Методы изменяют текущий объект | Методы возвращают новый объект                    |
| Пример              | `list`, `dict`, `set`          | `int`, `str`, `tuple`                             |

Проверьте себя: Функция получает список аргументом и делает lst += [1] внутри — изменения будут видны снаружи функции? А если внутри сделать lst = lst + [1]?
3
MarkdownРазбор концепции58 строк

id(): проверка тождества объектов на mutable и immutable примерах

less_15__memory__deep_copy/theory_03__id.md

Показывает работу id() и оператора is на двух параллельных примерах — immutable (a = b = 10000, затем b += 1) и mutable (a = b = [1], затем b += [1]). Ключевой контраст: у чисел b += 1 создаёт новый объект с новым id, а у списков b += [1] меняет объект на месте, поэтому id(a) и id(b) остаются равны и a is b по-прежнему True.

  • id(object) — уникальный идентификатор, в CPython совпадает с адресом в памяти
  • a = b = 10000; b += 1 — id(b) меняется, id(a) остаётся
  • a = b = [1]; b += [1] — id(a) == id(b) остаётся True
  • is сравнивает id, == сравнивает значение
Показать начало файла (58 строк всего)

## Функция `id()`

`id(object)` — встроенная функция Python, которая возвращает  
**уникальный идентификатор объекта** в текущей сессии.

* В CPython этот идентификатор обычно совпадает с **адресом объекта в памяти (heap)**.
* Идентификатор **не меняется** в течение жизни объекта.
* **object** — любой объект Python (изменяемый или неизменяемый).
* Возвращает **целое число** (обычно адрес объекта в памяти).
* 
---

## Неизменяемые (`immutable`) типы данных

```python
a = b = 10000

print(a)  # 10000
print(b)  # 10000
print(id(a))  # 140698800665872
print(id(b))  # 140698800665872
print(id(a) == id(b))   # True, объекты одинаковые
print(a is b)           # True, a и b ссылаются на один и тот же объект

b += 1

print(a)  # 10000
print(b)  # 10001
print(id(a))  # 140698800665872
print(id(b))  # 140465933987088
print(id(a) == id(b))  # False, объекты разные
print(a is b)          # False, a и b ссылаются на РАЗНЫЕ объекты
```

---

## Изменяемые (`mutable`) типы данных

```python
a = b = [1]

print(a)  # [1]
print(b)  # [1]
print(id(a))  # 140017577855744
print(id(b))  # 140017577855744
print(id(a) == id(b))   # True, объекты одинаковые
print(a is b)           # True, a и b ссылаются на один и тот же объект

b += [1]

print(a)  # [1, 1]  --> объект a отражает изменения объекта b
print(b)  # [1, 1]
print(id(a))  # 140017577855744
print(id(b))  # 140017577855744
print(id(a) == id(b))  # True, объекты одинаковые
print(a is b)          # True, a и b ссылаются на один и тот же объект
```
Проверьте себя: Почему b += 1 для чисел меняет id(b), а b += [1] для списков — нет, хотя оба раза используется один и тот же оператор +=?
4
MarkdownРазбор концепции71 строк

Оператор del: что он удаляет на самом деле

less_15__memory__deep_copy/theory_04__del.md

Разбирает del как оператор, который удаляет имя или ссылку, а не сам объект напрямую — объект уходит в сборку мусора только когда счётчик ссылок падает до нуля. Отдельно показана разница между del numbers[:] (очищает содержимое, объект списка остаётся) и del numbers (удаляет саму переменную из пространства имён, обращение к ней даёт NameError).

  • del obj[index], del obj[start:end], del obj.attr — универсальный синтаксис
  • del numbers[2] — удаление элемента по индексу
  • del numbers[:] — очистка содержимого, аналог .clear()
  • del numbers — удаление имени целиком, NameError при обращении
  • Разница: del obj[:] сохраняет объект, del obj убирает ссылку
Показать начало файла (71 строк всего)
## Оператор `del`


**`del`** — это встроенный оператор Python, который удаляет объект или ссылку на объект.

* Универсальный оператор, который применяется ко всем типам данных, включая 
  * переменные, 
  * элементы контейнеров, 
  * срезы, 
  * атрибуты объектов 
  * и ключи словарей.
* После удаления переменной сам объект может быть удалён из памяти, 
  * если на него больше нет других ссылок (срабатывает сборщик мусора).

## Синтаксис:

```python
del obj
```

или

```python
del obj[index]         # удалить элемент по индексу
del obj[start:end]     # удалить срез
del obj.attr           # удалить атрибут объекта
```

---

## Примеры

```python
numbers = [10, 20, 30, 40, 50]

# Удаление элемента по индексу
del numbers[2]        # удаляем 30
print(numbers)        # [10, 20, 40, 50]

# Удаление среза
del numbers[1:3]      # удаляем элементы 20, 40
print(numbers)        # [10, 50]

# Удаляем все элементы списка (аналог метода .clear()
del numbers[:]        # удаляем элементы 10, 50
print(numbers)        # []

# Удаление всей переменной
del numbers
# print(numbers)      # NameError, переменная больше не существует
```

## Выводы

1. `del` может удалять:

   * отдельные элементы списка по индексу,
   * диапазон элементов списка (слайсинг или срезы),
   * все элементы списка,
   * переменные целиком.
…
Проверьте себя: После del numbers[:] переменная numbers всё ещё существует и указывает на пустой список — а после del numbers что произойдёт при print(numbers)?
5
MarkdownРазбор концепции87 строк

Shallow copy и deep copy на вложенных списках

less_15__memory__deep_copy/theory_05__copy__vs__deep_copy.md

Сравнивает поверхностное копирование (.copy(), срез [:], copy.copy(), list()) и глубокое (copy.deepcopy()) на примере списка списков. Демонстрирует ключевую ловушку: при shallow copy изменение элемента верхнего уровня (append нового подсписка) копию не затрагивает, а изменение вложенного элемента (original[0][0] = 0) отражается во всех поверхностных копиях, потому что они делят ссылки на вложенные объекты.

  • Четыре способа shallow copy: [:], .copy(), copy.copy(), list()
  • original.append([5,6]) — верхний уровень копий не меняется
  • original[0][0] = 0 — вложенный объект меняется во всех shallow-копиях
  • copy.deepcopy() — рекурсивное копирование, изменения original копию не затрагивают
  • Итоговая таблица: копирование верхнего уровня vs вложенных объектов
Показать начало файла (87 строк всего)
## Виды копирования двумерных изменяемых объектов

1. **Копирование (`shallow copy`, поверхностное копирование)**

   * Создаёт новый объект **верхнего уровня**, но **вложенные объекты остаются ссылками на исходные**.
   * Любое изменение вложенных объектов отражается в обеих копиях.
   * Используется:

     * 1.1. встроенный метод `.copy()` (для списков, словарей, set)
     * 1.2. слайсинг (срезы) `[:]` (для списков)
     * 1.3. функция `copy.copy()` из модуля `copy`
     * 1.3. функция `list()`

2. **Глубокое копирование (deep copy)**

   * Создаёт **новый объект и рекурсивно копирует все вложенные объекты**.
   * Изменение в одном объекте **не влияет на другой**.
   * Используется:

     * функция `copy.deepcopy()` из модуля `copy`.

---

## Примеры

### 1 Поверхностное копирование (`shallow copy`)

```python
import copy

original = [[1, 2], [3, 4]]

# Способ 1: через срез
shallow1 = original[:]

# Способ 2: .copy()
shallow2 = original.copy()

# Способ 3: через copy.copy()
shallow3 = copy.copy(original)

# Способ 4: через list()
shallow4 = list(original)

# Изменение верхнего уровня
original.append([5,6])
print(shallow1)   # [[1,2], [3,4]] — верхний уровень копии не изменился
print(shallow2)   # [[1,2], [3,4]] — верхний уровень копии не изменился
print(shallow3)   # [[1,2], [3,4]] — верхний уровень копии не изменился
print(shallow3)   # [[1,2], [3,4]] — верхний уровень копии не изменился

# Изменение вложенного объекта
original[0][0] = 0
print(shallow1)   # [[0, 2], [3, 4]] — вложенный объект копии изменился
print(shallow2)   # [[0, 2], [3, 4]] — вложенный объект копии изменился
print(shallow3)   # [[0, 2], [3, 4]] — вложенный объект копии изменился
print(shallow4)   # [[0, 2], [3, 4]] — вложенный объект копии изменился
```

---
…
Проверьте себя: Если original = [[1, 2], [3, 4]] и shallow = original.copy(), какой код изменит shallow, а какой — нет: original.append([5,6]) или original[0].append(99)?
6
MarkdownРазбор концепции114 строк

Пространство имён, ссылки и что реально удаляет объект из памяти

less_15__memory__deep_copy/theory_06__What_deletes_an_object.md

Прослеживает полную цепочку: переменная — это имя в пространстве имён (locals()/globals()), которое хранит ссылку на объект в куче, причём для mutable и immutable типов принципиальной разницы в этом нет. На примерах x = y = [1,2,3]; del y и x = y = 5; del y показывает, что del удаляет только имя, а сам объект удаляется сборщиком мусора лишь когда счётчик ссылок на него падает до нуля.

  • Переменная = имя в namespace, объект = в куче
  • x = [1,2,3] — x хранит ссылку, а не сам список
  • b = a; del a — объект жив, пока жива ссылка b
  • Счётчик ссылок = 0 → объект становится мусором для GC
  • Параллель: одинаковое поведение del для списка и для числа
Показать начало файла (114 строк всего)
## Что именно удаляет объект?

## 1. Что такое переменная в Python?

Переменная `x` — это **имя (ссылка)**, которое хранится в *пространстве имён* (namespace).  
Она указывает на объект (в памяти).

```python
x = [1, 2, 3]   # x → ссылка на объект "список"
y = 10          # y → ссылка на объект "число 10"
```

---

## 2. Что именно хранится в переменной для разных типов данных?

* **Изменяемые типы (mutable)** хранит **ссылку на объект в памяти**.
  Объект можно менять через эту ссылку:

  ```python
  x = [1, 2, 3]
  x.append(4)   # меняется объект, а не ссылка
  ```

* **Неизменяемые типы (immutable)** — ТОЖЕ хранит ссылку, но сам объект изменить нельзя.  
* При "изменении" создаётся **новый объект**, а переменная начинает ссылаться на него:

  ```python
  x = "hello"
  x = x + "!"
  # Теперь x указывает на новый объект "hello!"
  ```

⚠️ МЕГА Важно: для Python **разницы нет** — всегда хранится **ссылка**.    
Различие только в том, можно ли менять объект по этой ссылке.

---

## 3. Где хранятся переменные?

* В Python переменные (`x`, `y`, `my_list`) живут в **пространстве имён** (namespace).

  * Внутри функции — в **локальном пространстве имён** (словарь `locals()`).
  * В модуле — в **глобальном пространстве имён** (словарь `globals()`).
* Сами **объекты** (списки, строки, числа) хранятся в **куче (heap)**.

То есть схема такая:

```
namespace (словарь)
 ├── "x" ──► [1, 2, 3]
 └── "y" ──► 10
```

---

## 4. Что делает `del x`?

* Удаляет имя `x` из пространства имён.
* Сам объект **не удаляется сразу**, если на него ещё есть другие ссылки.
…
Проверьте себя: В примере x = y = [1, 2, 3]; del y; print(x) — почему print(x) не падает с ошибкой, хотя y было удалено?