Изменяемые и неизменяемые типы, передача аргументов

⏱ 30 мин · 🧩 заданий: 5

Две группы типов

Неизменяемые (immutable) Изменяемые (mutable)
int, float, complex, bool list
str, bytes dict
tuple, frozenset set
None bytearray
экземпляры обычных классов

У неизменяемого объекта значение нельзя поменять после создания. Любая «модификация» создаёт новый объект.

s = "кот"
print(id(s))
s = s + "ик"         # новый объект; имя s перепривязано
print(id(s), s)

nums = [1, 2]
print(id(nums))
nums.append(3)       # тот же объект изменился
print(id(nums), nums)

+=: новый объект или тот же?

x += y вызывает x.__iadd__(y), если он есть (изменяет на месте), иначе x = x + y (новый объект):

a = [1, 2]
b = a
a += [3]          # list.__iadd__ — на месте
print(a, b, a is b)

x = (1, 2)
y = x
x += (3,)         # у tuple нет __iadd__ → x = x + (3,) — новый объект
print(x, y, x is y)

a = [1, 2]
b = a
a = a + [3]       # + всегда создаёт новый список
print(a, b, a is b)

Пройдите 👣 Пошагово — номера объектов показывают, где объект новый, а где тот же.

Передача аргументов: call by sharing

При вызове f(obj) параметр функции становится ещё одним именем того же объекта.

def modify(lst, num, text):
    lst.append(99)       # изменение объекта — видно снаружи
    num += 1             # перепривязка локального имени — снаружи не видно
    text = text.upper()  # то же самое
    lst = ["новый"]      # перепривязка — снаружи не видно

my_list, my_num, my_text = [1], 10, "abc"
modify(my_list, my_num, my_text)
print(my_list, my_num, my_text)
вызывающий: my_list ──▶ #1 [1, 99] ◀── lst (до lst = ["новый"]) функция: lst ──▶ #2 ["новый"] (после)

Правило:

  • изменить объект (append, update, присвоить атрибут, элемент) — изменение видно вызывающему;
  • присвоить параметру новое значение — меняется только локальное имя.

На собеседовании

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

Как не испортить аргумент

Функция, которая молча меняет переданный список, — источник трудноуловимых ошибок. Варианты:

def normalized_bad(scores):
    scores.sort()                  # побочный эффект!
    return scores

def normalized_good(scores):
    return sorted(scores)          # новый список

data = [3, 1, 2]
print(normalized_good(data), data)
normalized_bad(data)
print(data)                         # исходные данные изменены
  1. Возвращайте новый объект (sorted, включения, {**d, ...}).
  2. Если нужна правка — сделайте копию в начале: items = list(items).
  3. Если функция специально меняет аргумент — пусть возвращает None (как list.sort) и это будет ясно из имени.
  4. Принимайте неизменяемые типы (tuple, frozenset) там, где менять не нужно.

Неизменяемость «вглубь» не гарантирована

t = (1, 2, [3])
try:
    t[2] += [4]
except TypeError as e:
    print("TypeError:", e)
print(t)

Кортеж неизменяем, но список внутри — нет. Эта головоломка показывает, что += выполняется в два шага: изменить объект, потом присвоить результат.

Почему неизменяемость полезна

  • Хешируемость: неизменяемые объекты можно использовать как ключи словаря и элементы множеств.
  • Безопасность: можно передать объект куда угодно — его не испортят.
  • Потокобезопасность: неизменяемые данные не нужно защищать блокировками.
  • Оптимизации: интернирование строк, кеш малых чисел, константы в байткоде.

Итог

  • Неизменяемые: числа, str, bytes, tuple, frozenset, None. Изменяемые: list, dict, set, обычные объекты.
  • += меняет на месте, если есть __iadd__ (list), иначе создаёт новый объект (int, str, tuple).
  • Аргументы передаются по ссылке на объект: мутации видны снаружи, перепривязка — нет.
  • Не мутируйте аргументы без явной необходимости — возвращайте новые объекты.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Заполнить таблицу

Изменяемый тип или нет?

ИзменяемыйНеизменяемый
list
str
tuple
dict
int
set
frozenset
bytearray
Отметьте ячейку — «+», пустая — «−».
Задание 2 Один вариант ответа

Что выведет код?

def f(x, items):
    x += 1
    items += [1]

a, b = 0, []
f(a, b)
print(a, b)
Задание 3 Программа на Python

Функция add_tax сейчас портит переданный список цен. Перепишите её так, чтобы она возвращала новый список цен с налогом 20% (округление до 2 знаков), а исходный не менялся.

Какие тесты проверяют код (3)
assert add_tax([100, 50]) == [120.0, 60.0]
p = [10, 20]; r = add_tax(p); assert p == [10, 20], "Исходный список не должен меняться"
p = [10]; assert add_tax(p) is not p, "Нужно вернуть новый список"
Задание 4 Один вариант ответа

Как точнее всего назвать способ передачи аргументов в Python?

Задание 5 Один вариант ответа

Что выведет код?

t = (1, 2, [3])
try:
    t[2] += [4]
except TypeError:
    pass
print(t)

Войдите, чтобы отмечать пройденные уроки.