ПрограммированиеBackend разработчик

Что такое передачa аргументов по ссылке и по значению в Python? Как Python реализует этот механизм и почему важно различать их при проектировании функций?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Понимание того, каким образом Python передает аргументы в функции, крайне важно для предотвращения неожиданных изменений данных и для корректного проектирования кода.

История вопроса

В традиционных языках программирования, таких как C или Java, используется передача по значению (copy by value) или по ссылке (copy by reference). Однако в Python иная модель — call by object reference (иногда называют "call by sharing").

Проблема

Многие разработчики ошибочно думают, что Python всегда или по ссылке, или по значению передает аргументы. Это неизбежно приводит к ситуациям, когда изменяемые объекты неожиданно модифицируются в вызывающем коде.

Решение

В Python значения параметров функции являются ссылками на объекты, которые передаются в функцию. Это означает:

  • Если объект изменяемый (mutable: list, dict, set…) — внутри функции его можно модифицировать, и это отразится снаружи.
  • Если объект неизменяемый (immutable: int, str, tuple, frozenset), попытка изменить его внутри функции приведет к созданию нового объекта и не затронет внешний.

Пример:

# list - изменяемый (mutable) def add_item(lst): lst.append(42) my_list = [1, 2, 3] add_item(my_list) print(my_list) # [1, 2, 3, 42] # int - неизменяемый (immutable) def add_num(x): x = x + 1 num = 10 add_num(num) print(num) # 10

Ключевые особенности:

  • Изменяемые объекты могут быть изменены внутри функции — эти изменения видны снаружи.
  • Неизменяемые объекты не затрагиваются функцией — только создаются новые объекты.
  • Python никогда не копирует аргументы автоматически, даже изменяемые структуры всегда передаются "по ссылке".

Вопросы с подвохом.

Передаются ли в Python всегда аргументы по ссылке?

Нет, в Python передаются ссылки на объекты, и то, как ведет себя объект, зависит от того, изменяемый он или нет. Неизменяемый объект при любом изменении создаст новый объект.

Можно ли в функции переприсвоить изменяемый аргумент, и чтобы это повлияло на внешний объект?

Нет. Если вы внутри функции присваиваете параметру новое значение, внешний объект никак не изменится — вы лишь меняете локальную ссылку.

Пример:

def reassign_list(lst): lst = [99, 100] my_list = [1, 2, 3] reassign_list(my_list) print(my_list) # [1, 2, 3]

Почему функция, которая принимает list по умолчанию, может работать странно при повторных вызовах?

Потому что значение по умолчанию создается один раз — при определении функции, и если его изменить (например, добавить элемент), оно изменится для всех последующих вызовов.

def add_element(x, cache=[]): cache.append(x) return cache print(add_element(1)) # [1] print(add_element(2)) # [1, 2]

Типовые ошибки и анти-паттерны

  • Использование изменяемых аргументов по умолчанию (как в последнем примере).
  • Ожидание, что функция не изменит переданный list или dict, хотя она его модифицирует.
  • Перепутывание эффектов функций при работе с изменяемыми и неизменяемыми объектами.

Пример из жизни

Негативный кейс

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

Плюсы:

  • Быстрая работа, нет копирования данных.

Минусы:

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

Позитивный кейс

Программист явно копирует список внутри функции, если надо что-то вернуть, но не менять оригинал:

def process_data(data): data = data.copy() # или list(data) # безопасная работа с копией data.append('отчёт') return data

Плюсы:

  • Нет нежелательных побочных эффектов, оригинал защищён.

Минусы:

  • При больших объектах — затраты памяти/времени на копирование.