Как передаются объекты в функцию в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как передаются объекты в функцию в Python
Это один из самых неправильно понимаемых аспектов Python. Нет, это не pass-by-value и не pass-by-reference. За 10+ лет я видел столько багов по этой причине.
1. Pass-by-Object-Reference (или Pass-by-Assignment)
Python использует уникальный механизм — не pass-by-value и не pass-by-reference:
def show_id(obj):
print(f"ID внутри функции: {id(obj)}")
x = [1, 2, 3]
print(f"ID снаружи: {id(x)}")
show_id(x)
# ID снаружи: 140735648512345
# ID внутри функции: 140735648512345 ← ТОТ ЖЕ ОБЪЕКТ
Объект передаётся по ссылке, но сама переменная передаётся по значению.
2. Изменяемые объекты (Mutable)
Для изменяемых объектов (list, dict, set, custom objects) изменения внутри функции влияют на оригинал:
def modify_list(lst):
lst.append(4) # Изменяем содержимое
print(f"Внутри: {lst}")
my_list = [1, 2, 3]
print(f"До: {my_list}")
modify_list(my_list)
print(f"После: {my_list}")
# До: [1, 2, 3]
# Внутри: [1, 2, 3, 4]
# После: [1, 2, 3, 4] ← ИЗМЕНЕНО!
Но если переприсвоить переменную в функции:
def bad_modify(lst):
lst = [999] # Переприсваиваем переменную, не объект
print(f"Внутри: {lst}")
my_list = [1, 2, 3]
bad_modify(my_list)
print(f"После: {my_list}") # [1, 2, 3] — НЕ ИЗМЕНИЛОСЬ
Почему? Потому что мы не изменили сам объект, а переприсвоили локальную переменную.
3. Неизменяемые объекты (Immutable)
Для неизменяемых объектов (int, str, tuple) нельзя изменить содержимое:
def try_modify_int(n):
n = n + 1 # Создаёт НОВЫЙ объект
print(f"Внутри: {n}")
x = 5
print(f"До: {x}")
try_modify_int(x)
print(f"После: {x}") # 5 — НЕ ИЗМЕНИЛОСЬ
# Объяснение
print(f"ID x: {id(x)}")
def show_ids(n):
print(f"ID параметра: {id(n)}")
n = n + 1
print(f"ID нового объекта: {id(n)}")
show_ids(x)
# ID x: 140735648512345
# ID параметра: 140735648512345 ← тот же
# ID нового объекта: 140735648512346 ← другой
4. Таблица: Mutable vs Immutable
Изменяемые (Mutable):
- list
- dict
- set
- bytearray
- custom objects (обычно)
Неизменяемые (Immutable):
- int
- float
- str
- tuple
- frozenset
- bool
- None
- bytes
5. Практический пример с использованием Mutable
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def celebrate_birthday(user):
user.age += 1 # Изменяем атрибут объекта
user = User("Иван", 30)
print(f"До: {user.age}")
celebrate_birthday(user)
print(f"После: {user.age}") # 31 — ИЗМЕНИЛОСЬ
# Но если переприсвоить переменную:
def bad_celebrate(user):
user = User("Петр", 25) # Новая переменная, новый объект
user = User("Иван", 30)
bad_celebrate(user)
print(f"После: {user.name}") # "Иван" — НЕ ИЗМЕНИЛОСЬ
6. Возврат значений — правильный способ
Если нужно вернуть новый объект:
# ❌ Изменение объекта в функции (побочный эффект)
def bad_increment(lst):
lst.append(1)
# ✅ Возврат нового объекта (чище)
def good_increment(lst):
return lst + [1]
numbers = [1, 2, 3]
result = good_increment(numbers)
print(f"Original: {numbers}") # [1, 2, 3]
print(f"Result: {result}") # [1, 2, 3, 1]
7. Копирование объектов
original = [1, 2, 3]
# ❌ Неправильно: просто ещё одна ссылка
shallow_ref = original
shallow_ref.append(4)
print(original) # [1, 2, 3, 4] — изменён!
# ✅ Поверхностная копия (shallow copy)
import copy
shallow_copy = copy.copy(original)
shallow_copy.append(5)
print(original) # [1, 2, 3, 4, 5] — не изменён
# ✅ Глубокая копия (deep copy) для вложенных объектов
nested = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(nested)
deep_copy[0].append(999)
print(nested) # [[1, 2], [3, 4]] — не изменён
print(deep_copy) # [[1, 2, 999], [3, 4]]
8. Побочные эффекты в функциях
# ❌ ПЛОХО: функция с побочными эффектами (hard to test)
def add_user_to_db(user, database):
database['users'].append(user) # Изменяет объект
return database # Путано
# ✅ ХОРОШО: явное изменение или новый объект
def get_updated_database(user, database):
"""Возвращает новую БД, не изменяя оригинал"""
new_db = copy.deepcopy(database)
new_db['users'].append(user)
return new_db
# Или с очевидным побочным эффектом:
def save_user(user, database):
"""ИЗМЕНЯЕТ database и ничего не возвращает"""
database['users'].append(user)
9. Параметры по умолчанию — опасность!
# ❌ ОПАСНО: mutable default argument
def bad_append(item, lst=[]):
lst.append(item) # Список переиспользуется!
return lst
print(bad_append(1)) # [1]
print(bad_append(2)) # [1, 2] — ОЖИДАЕМ [2]!
print(bad_append(3)) # [1, 2, 3]
# ✅ ПРАВИЛЬНО: None как умолчание
def good_append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(good_append(1)) # [1]
print(good_append(2)) # [2] — новый список
print(good_append(3)) # [3]
10. Практический пример: функция с классом
class Account:
def __init__(self, balance):
self.balance = balance
def transfer_money(from_account, to_account, amount):
"""Передача денег между счётами"""
# Объекты передаются по ссылке, поэтому изменения видны
if from_account.balance >= amount:
from_account.balance -= amount
to_account.balance += amount
return True
return False
acc1 = Account(1000)
acc2 = Account(500)
print(f"До: acc1={acc1.balance}, acc2={acc2.balance}")
transfer_money(acc1, acc2, 100)
print(f"После: acc1={acc1.balance}, acc2={acc2.balance}")
# До: acc1=1000, acc2=500
# После: acc1=900, acc2=600 ← ИЗМЕНЕНЫ
11. Когда объект "передаётся" в аргумент
def function(parameter):
pass
my_object = object() # Создаём объект
function(my_object) # Передаём ссылку на объект
# Что происходит:
# 1. my_object — переменная, содержащая ссылку на объект
# 2. function(my_object) — передаёт ЭТУ ССЫЛКУ
# 3. parameter = my_object — parameter становится ещё одной ссылкой
# 4. Обе переменные указывают на ОДИ И ТОТ ЖЕ объект
print(my_object is parameter) # Внутри функции: True
12. Порядок создания объектов при передаче
# При вызове функции:
result = process_data([1, 2, 3], {"key": "value"})
# Что происходит:
# 1. [1, 2, 3] — создаётся объект list
# 2. {"key": "value"} — создаётся объект dict
# 3. Передаются ссылки на эти объекты
# 4. Если функция не вернёт и не сохранит эти объекты,
# они станут недостижимы (garbage collection удалит)
Чеклист понимания передачи объектов
- Объекты передаются по ссылке — параметр указывает на тот же объект
- Переменные передаются по значению — переприсвоение не влияет на оригинал
- Для изменяемых типов — изменения в функции видны снаружи
- Для неизменяемых типов — нельзя изменить (можно только создать новый)
- Используй copy для защиты — если нужна независимая копия
- Избегай mutable defaults — всегда используй None
- Возвращай новые объекты — чище чем побочные эффекты
Золотое правило
Python передаёт объекты по ссылке. Если объект изменяемый, изменения видны везде. Если не хочешь побочных эффектов — используй copy() или возвращай новый объект. Mutable default arguments — это мина замедленного действия в вашем коде.