Передача аргументов в функции без 'по значению' и 'по указателю'
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
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.
On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course