В Python всё объект: число, строка, функция, класс, модуль. У каждого объекта есть идентичность (id), тип (type) и значение. Переменная не хранит объект, а только ссылается на него. Почти все вопросы первой части собеса крутятся вокруг этой мысли.
Переменная это имя, а не коробка
a = [1, 2]
b = a # второе имя для того же списка
b.append(3)
print(a) # [1, 2, 3]
Присваивание ничего не копирует, оно привязывает имя к объекту. Поэтому b.append видно через a. А вот b = b + [4] создаст новый список и перепривяжет только b.
Встроенные типы
| Неизменяемые | Изменяемые |
|---|---|
int, float, complex, bool | list |
str, bytes | dict |
tuple, frozenset | set, bytearray |
None (единственный объект NoneType) | пользовательские классы (по умолчанию) |
int в Python неограниченной длины: 2 ** 100 считается точно. bool это подкласс int, поэтому True + True == 2. float это IEEE 754 двойной точности, отсюда 0.1 + 0.2 != 0.3. Для денег берут decimal.Decimal или считают в копейках целыми.
Неизменяемость не значит «нельзя поменять переменную»
Неизменяемый объект нельзя поменять на месте. Любая «модификация» строки создаёт новую строку:
s = 'кот'
t = s
s += 'ы' # новый объект, t по-прежнему 'кот'
С изменяемыми += работает иначе: для списка это extend на месте.
x = [1]
y = x
x += [2] # изменили тот же список
print(y) # [1, 2]
Кортеж неизменяем, но может содержать изменяемые элементы: t = ([1], 2), и t[0].append(5) сработает. Такой кортеж нельзя положить в set или сделать ключом dict, потому что он не хешируемый.
is против ==
==сравнивает значения (вызывает__eq__).isпроверяет, что это один и тот же объект (одинаковыйid).
is используют для синглтонов: x is None, x is True (редко), sentinel. Для чисел и строк is даёт непредсказуемый результат: CPython кеширует малые целые от -5 до 256 и интернирует часть строк, но это деталь реализации, а не гарантия языка.
a = 256; b = 256
a is b # True в CPython, но полагаться нельзя
x = [1]; y = [1]
x == y, x is y # (True, False)
Истинность
Ложными считаются: None, False, ноль любого числового типа, пустые '', [], (), {}, set(), range(0). Всё остальное истинно, включая '0', 'False' и [0]. Свой класс управляет этим через __bool__ или __len__.
and и or возвращают не bool, а один из операндов: '' or 'гость' даст 'гость', 0 and 1/0 даст 0 и деление не выполнится.
Копирование
import copy
src = [[1, 2], [3]]
shallow = src.copy() # или list(src), src[:]
deep = copy.deepcopy(src)
src[0].append(9)
shallow[0] # [1, 2, 9]: внутренние списки общие
deep[0] # [1, 2]
Поверхностная копия создаёт новый внешний контейнер, но кладёт в него те же внутренние объекты. Глубокая рекурсивно копирует всё, учитывает циклические ссылки и может быть медленной.
Частые ошибки
[[0] * 3] * 3создаёт три ссылки на один и тот же внутренний список. Правильно:[[0] * 3 for _ in range(3)].if x == Noneвместоif x is None.- Проверка
if not itemsдля значения, где0или''допустимы: используйтеis None. - Сравнение
floatчерез==: беритеmath.isclose. - Мнение, что
tupleвсегда хешируем.
Что спросят на собесе
- Чем изменяемые типы отличаются от неизменяемых, приведите примеры.
- Разница между
isи==, почемуa is bдля чисел иногдаTrue. - Что будет при
b = aдля списка и как сделать копию, чем поверхностная отличается от глубокой. - Почему
0.1 + 0.2 != 0.3и что с этим делать. - Что считается ложным и что возвращают
andиor.