← Назад к вопросам

Какие плюсы и минусы передачи переменных по ссылки?

1.3 Junior🔥 151 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Передача переменных по ссылке в Python

Важно понимать, что в Python нет явной передачи "по ссылке" или "по значению" как в C++. Есть концепция передача по значению ссылки (pass-by-object-reference).

Как работает

Переменная в Python — это ссылка на объект в памяти.

# При передаче в функцию передаётся ссылка на объект
def modify(obj):
    obj['key'] = 'modified'  # изменяем сам объект

data = {'key': 'original'}
modify(data)
print(data)  # {'key': 'modified'} — изменилось!

# Если переменная ссылается на immutable тип, изменение не отражается
def modify_int(x):
    x = x + 1  # создаётся новый объект
    return x

num = 10
modify_int(num)
print(num)  # 10 — не изменилось!

Плюсы передачи по ссылке

1. Эффективность памяти

Не копируем данные, а передаём ссылку.

# ❌ Плохо: копирование больших данных
def process_list_copy(items):
    items_copy = items.copy()  # копируем весь список
    items_copy.append('new')
    return items_copy

large_list = list(range(1000000))
result = process_list_copy(large_list)  # O(N) памяти

# ✅ Хорошо: работа со ссылкой
def process_list_ref(items):
    # Работаем со ссылкой, копию не создаём
    count = len(items)
    first = items[0] if items else None
    return count, first

result = process_list_ref(large_list)  # O(1) памяти

2. Изменение структур на месте (in-place)

Мутирующие операции без создания новых объектов.

# ✅ Эффективно: изменение на месте
def sort_in_place(items):
    items.sort()  # O(1) памяти, O(N log N) времени

data = [3, 1, 2]
sort_in_place(data)
print(data)  # [1, 2, 3]

# Для сравнения, создание нового списка
def sort_copy(items):
    return sorted(items)  # O(N) памяти

3. Совместное использование данных

Несколько переменных могут ссылаться на один объект.

original = [1, 2, 3]
reference = original  # одна ссылка, один объект

original.append(4)
print(reference)  # [1, 2, 3, 4] — видим изменения
print(original is reference)  # True — одно и то же

4. Обновление состояния

Функция обновляет объект без возврата.

class UserManager:
    def __init__(self, users):
        self.users = users  # ссылка на список
    
    def activate_user(self, user_id: int):
        for user in self.users:
            if user['id'] == user_id:
                user['active'] = True  # мутирует исходный объект

users = [{'id': 1, 'active': False}, {'id': 2, 'active': False}]
manager = UserManager(users)
manager.activate_user(1)

print(users)  # [{'id': 1, 'active': True}, ...] — изменилось!

Минусы передачи по ссылке

1. Неожиданные побочные эффекты

Функция может менять данные вызывающего кода.

# ❌ Плохо: непредсказуемое поведение
def process_user(user):
    user['email'] = 'modified@example.com'  # мутирует!
    return user

my_user = {'name': 'John', 'email': 'john@example.com'}
process_user(my_user)
print(my_user)  # email изменился! Ожидали ли?

# ✅ Хорошо: явная мутация или возврат копии
def process_user_copy(user):
    updated = user.copy()  # создаём копию
    updated['email'] = 'modified@example.com'
    return updated

my_user = {'name': 'John', 'email': 'john@example.com'}
new_user = process_user_copy(my_user)
print(my_user)  # не изменился
print(new_user)  # изменился

2. Bugs с общими ссылками

Мутация в одном месте влияет на другое.

# ❌ Плохо: шаринг mutable списка
def create_users(count: int):
    default_settings = {'notifications': True, 'theme': 'dark'}
    users = []
    
    for i in range(count):
        user = {'id': i, 'settings': default_settings}  # одна ссылка!
        users.append(user)
    
    return users

users = create_users(3)
users[0]['settings']['theme'] = 'light'
print(users[1]['settings']['theme'])  # 'light'! Неожиданно!

# ✅ Хорошо: создаём копию для каждого
def create_users_correct(count: int):
    users = []
    
    for i in range(count):
        default_settings = {'notifications': True, 'theme': 'dark'}
        user = {'id': i, 'settings': default_settings}  # новая копия
        users.append(user)
    
    return users

3. Сложность отладки

Изменения могут произойти неожиданно в разных местах.

# ❌ Трудно отследить, где изменился объект
def operation_a(data):
    data.append('a')  # мутирует

def operation_b(data):
    data.append('b')  # мутирует

def operation_c(data):
    data.append('c')  # мутирует

shared_list = []
operation_a(shared_list)
operation_b(shared_list)
operation_c(shared_list)
print(shared_list)  # ['a', 'b', 'c'] — из какой функции какой элемент?

# ✅ Хорошо: функциональный подход
def operation_a(data):
    return data + ['a']

def operation_b(data):
    return data + ['b']

def operation_c(data):
    return data + ['c']

result = []
result = operation_a(result)
result = operation_b(result)
result = operation_c(result)
print(result)  # ['a', 'b', 'c'] — явный порядок

4. Race conditions в многопоточности

Одновременный доступ к mutable объекту без синхронизации.

import threading

# ❌ Плохо: race condition
shared_list = []

def add_item(value):
    # Несейф: проверка и добавление не атомарны
    if len(shared_list) < 10:
        shared_list.append(value)

threads = [
    threading.Thread(target=add_item, args=(i,))
    for i in range(20)
]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(len(shared_list))  # может быть > 10 (race condition)

# ✅ Хорошо: с синхронизацией
import threading

shared_list = []
lock = threading.Lock()

def add_item_safe(value):
    with lock:  # критическая секция
        if len(shared_list) < 10:
            shared_list.append(value)

threads = [
    threading.Thread(target=add_item_safe, args=(i,))
    for i in range(20)
]

for t in threads:
    t.start()
for t in threads:
    t.join()

print(len(shared_list))  # ровно 10

Best Practices

1. Явная документация о мутации

def process_users(users: list) -> list:
    """Процесс пользователей.
    
    Args:
        users: Список пользователей (МУТИРУЕТСЯ)
    
    Returns:
        Тот же список с изменениями
    
    Warning:
        Функция изменяет переданный список!
    """
    for user in users:
        user['processed'] = True
    return users

2. Используй immutable типы когда возможно

from typing import NamedTuple
from dataclasses import dataclass

# ✅ NamedTuple — immutable
class Point(NamedTuple):
    x: int
    y: int

p = Point(1, 2)
# p.x = 3  # TypeError: can't set attribute

# ✅ dataclass с frozen=True
@dataclass(frozen=True)
class User:
    id: int
    name: str

user = User(1, 'John')
# user.name = 'Jane'  # FrozenInstanceError

3. Копируй когда нужно чистый API

from copy import deepcopy

def format_response(data: dict) -> dict:
    """Форматирует данные БЕЗ изменения оригинала."""
    result = deepcopy(data)  # глубокая копия
    result['timestamp'] = datetime.now()
    result['version'] = '1.0'
    return result

original = {'name': 'John'}
formatted = format_response(original)
print(original)  # не изменился
print(formatted)  # с новыми полями

4. Функциональное программирование

from typing import Callable

# ✅ Чистые функции
def add_item(items: list, value: str) -> list:
    return items + [value]  # новый список

def remove_item(items: list, value: str) -> list:
    return [x for x in items if x != value]

# Используй как
items = ['a', 'b']
items = add_item(items, 'c')     # ['a', 'b', 'c']
items = remove_item(items, 'b')  # ['a', 'c']

Итого

АспектПлюсМинус
ПамятьЭкономия-
СкоростьБыстро-
Побочные эффекты-Неожиданные изменения
Отладка-Сложнее
Многопоточность-Race conditions

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

Какие плюсы и минусы передачи переменных по ссылки? | PrepBro