UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

Передача аргументов в функции без 'по значению' и 'по указателю'

core объяснить и предсказать, будет ли функция мутировать переданный ей list/dict, глядя на её тело

В C у вызова функции есть ровно две модели, и вы выбираете между ними на месте вызова:

  • по значению: void f(int val) — внутри работает копия, изменения снаружи не видны;
  • по указателю: void f(int *p) — передан адрес, *p = 10 меняет данные вызывающего.

Модель видна прямо в сигнатуре. В Python этого выбора нет — есть одна модель, которую называют call by object / call by sharing («передача ссылки на объект»). Проще всего думать так: вызов f(arg) выполняет неявное присваивание param = arg, поэтому все правила предыдущего урока действуют без изменений. Параметр внутри функции становится ещё одним именем для того же объекта; копирования объекта не происходит.

Дальнейшее поведение зависит не от того, что передано, а от того, что вы делаете с параметром внутри функции:

  • Если вы мутируете объект через параметр (lst.append(...), d[k] = v, obj.attr = ...) — изменение видно снаружи, потому что параметр и внешнее имя ссылаются на один объект. Это похоже на передачу указателя в C.
  • Если вы перепривязываете параметр (lst = [...], lst = lst + [x]) — вы заставили локальное имя lst указывать на новый объект. Внешнее имя как ссылалось на старый объект, так и продолжает. В C *p = new_value изменило бы данные по адресу; здесь вы просто «отвязали» локальную переменную.

Именно второй пункт — источник систематической ошибки C-программиста: интуиция «я получил указатель на данные вызывающего, значит присваивание параметру = запись через указатель» здесь ломается. В Python присваивание параметру всегда локальная перепривязка, независимо от типа объекта. Аналога «указателя на указатель» (int **p), которым в C меняют саму ссылку вызывающего, в Python нет — единственный способ отдать новый объект наружу — return.

sequenceDiagram
    participant Caller as вызывающий (x -> Obj1)
    participant Func as функция (lst -> Obj1)
    Caller->>Func: f(x)
    Note over Func: lst.append(9) — мутация Obj1
    Func->>Caller: Obj1 изменён, виден снаружи
    Note over Func: lst = [] — перепривязка lst к Obj2
    Func->>Caller: Obj1 снаружи не тронут
Мутация через параметр видна вызывающему, а перепривязка параметра — только локальная и невидимая снаружи

Разобранный пример

def mutate(lst):
    lst.append(99)      # мутация объекта in place

def rebind(lst):
    lst = [1, 2, 3]     # перепривязка локального имени lst

data = [0]
mutate(data)
print(data)             # [0, 99] — мутация видна

data2 = [0]
rebind(data2)
print(data2)            # [0] — перепривязка не видна

Рассуждение: в обоих случаях lst внутри функции изначально ссылается на тот же объект, что и data/data2. mutate вызывает метод, меняющий содержимое объекта — объект один, значит изменение видно везде. rebind привязывает имя lst к новому объекту [1, 2, 3], а data2 продолжает ссылаться на старый список.

Классическая жертва этой путаницы — попытка написать swap как в C:

def swap(a, b):
    a, b = b, a   # меняет местами ЛОКАЛЬНЫЕ имена a и b, а не переменные вызывающего

x, y = 1, 2
swap(x, y)
print(x, y)   # всё ещё 1 2 — swap не сработал

В C для этого передали бы int*. В Python единственный способ вернуть новые значения — return b, a и переприсвоить снаружи.

Попробуй сейчас

Определите для каждой функции, мутирует ли она переданный список, глядя только на тело:

def f1(items):
    items += [0]

def f2(items):
    items = items + [0]

def f3(items):
    items[:] = items + [0]

def f4(items):
    items.extend([0])

Ожидаемый результат: f1 мутирует (+= для списка выполняется на месте, как .extend). f2 не мутирует (+ создаёт новый список, дальше — перепривязка локального имени). f3 мутирует (присваивание срезу [:] заменяет содержимое существующего объекта in place). f4 мутирует (.extend — явная мутация). Обратите внимание: f1 и f2 почти идентичны синтаксически (+= против +), но ведут себя противоположно — самая частая ловушка для списков.

Получилось, если…

Вы разобрались, если по одному взгляду на тело функции отличаете «операция на объекте in place» (мутирует, видно снаружи) от «присваивание имени параметра» (не видно снаружи), знаете, что += для списков — это первое, и понимаете, что для отдачи наружу нового объекта нужен return.

Вывод

Аргумент в Python — это не значение и не явный указатель, а ещё одно имя для того же объекта. Мутация объекта видна вызывающему; перепривязка параметра — локальна и невидима. += для изменяемых типов — мутация in place, а не «то же самое, что x = x + ...».

AI-generated · source-grounded review

🛡 Fact-checked: 4 risky claims verified · 4 removed · confidence: high · figures: 2
[verified] В Python передаётся ссылка на объект; параметр — ещё одно имя, изменяемые аргументы можно мутировать внутри функции
База знаний: «в Python передаётся ссылка на объект… изменяемые аргументы могут быть модифицированы внутри функции»
[verified] Присваивание параметру внутри функции — локальная перепривязка, невидимая снаружи; для отдачи нового объекта нужен return
Прямое следствие модели привязок из базы знаний; согласуется с семантикой присваивания
[softened] `+=` для списка вызывает `__iadd__` и мутирует объект in place
Поведение верно, но деталь про `__iadd__` отсутствует в базе знаний — формулировка упрощена до «выполняется на месте, как .extend»
[removed] Конкретные значения id в выводах примеров (140732749799568, 2266218151232 …)
Невоспроизводимые конкретные числа, не подтверждаемые базой знаний
[verified] sequenceDiagram: мутация Obj1 через параметр видна вызывающему, перепривязка lst → Obj2 не видна
Направления и отношения соответствуют модели call-by-sharing из базы знаний
[verified] SVG: после `items = [99, 100]` локальное имя items → новый список, my_data → прежний список
Согласуется с описанием привязок и локальной перепривязки
[removed] Из версии B удалены конкретные значения id (140732749799568, 2266218151232 и др.) в выводах примеров — невоспроизводимые числа, не подтверждаемые базой знаний
[removed] Формулировка про `+=` как «вызов `__iadd__`» упрощена до «выполняется на месте, как .extend», чтобы не опираться на деталь, отсутствующую в базе знаний
[removed] Вопрос квиза версии B о изменяемом значении по умолчанию (`history=[]`) исключён: тема не раскрыта в тексте урока, вопрос неотвечаем по содержанию
[removed] Декоративная mermaid-диаграмма версии B («вызов функции = присваивание») удалена: её метки не добавляют ничего к соседнему предложению

A second model re-checked this lesson's claims and corroborated 4 of 6 , and could not confirm 2 either way. This is incomplete corroboration, not a disagreement.

Key concepts: передача аргументов мутация объектов reference semantics
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course

Check yourself

1. Функция получает список и делает `lst = lst + [1]`. Увидит ли вызывающий код изменение?
2. Что выведет код? ```python def modify(data): data.append(4) data = [1, 2] my_list = [10, 20] modify(my_list) print(my_list) ```
3. Почему `def swap(a, b): a, b = b, a` не меняет переменные вызывающего кода?
4. Какое утверждение точнее всего описывает передачу аргументов в Python?
On your own course, these are marked as you answer