Python: Списки
Теория: Ссылки
В Python в отличие от других языков, нет разделения на примитивные типы и ссылочные. Все всегда передается по ссылке. Почему об этом нужно знать?
С точки зрения прикладного программиста, разница проявляется при изменении данных, их передаче и возврате из функций. Нужно держать в уме, что списки как коллекции хранят в себе не сами значения, а ссылки на них.
При этом списки сами тоже передаются по ссылкам. Чтобы убедиться в этом, создадим несколько переменных, содержащих один список, и посмотрим, как они меняются:
В примере выше мы создаем новую переменную items2 и записываем в нее ссылку на переменную items. Теперь две переменные ссылаются на один и тот же список. А значит изменив список в любой из переменных, он поменяется и для другой.
Ссылки
Что же вообще такое "ссылки"? Ссылка это уникальный идентификатор объекта, условный адрес в виртуальной памяти интерпретатора, по которому хранится значение переменной. Получить этот адрес можно функцией id()
Идентификатор — это обычное число. Но у каждого объекта свой уникальный идентификатор. Поэтому идентификаторы удобно использовать, чтобы отслеживать передачи ссылок на объект между разными участками кода — идентификатор объекта будет одним и тем же, по какой бы ссылке мы к объекту ни обращались:
Когда мы создаем переменную и записываем в нее значение, то мы как бы даем имя ссылке. Далее, мы присваиваем одну переменную другой, и даем еще одно, новое имя для этой же ссылки. Поэтому id(a) и id(b) возвращают одинаковый результат.
Оператор is проверяет равенство идентификаторов своих операндов. В этом примере обе переменные ссылаются на один объект, поэтому проверка a is b дает True.
Проверкой is в Python пользуются, когда мы имеем дело с так называемыми объектами-одиночками. Самые известные одиночки в Python, это True, False и None. Поэтому проверка на равенство None обычно пишется так:
Сравнение списков
Оператор == сравнивает списки, и любые другие объекты, по значению. То есть два списка будут равны, если имеют одинаковые значения:
Списки также можно сравнивать и по ссылке.
В этом примере, хоть списки и содержат одинаковые значения, но каждый список ссылается на свой адрес в виртуальной памяти.
Проектирование функций
Если передать список в какую-то функцию, которая его изменяет, то список тоже изменится. Ведь в функцию передается именно ссылка на список. Посмотрите на пример:
Проектируя функции, работающие со списками, есть два пути: менять исходный список или формировать внутри новый и возвращать его наружу. Какой лучше? В подавляющем большинстве случаев стоит предпочитать второй. Это безопасно. Функции, возвращающие новые значения, удобнее в работе, а поведение программы становится в целом более предсказуемым, так как отсутствуют неконтролируемые изменения данных.
Изменение списка может повлечь за собой неожиданные эффекты. Представьте себе функцию last(), которая извлекает последний элемент из списка. Она могла бы быть написана так:
Где-то в коде вы просто хотели посмотреть последний элемент. А в дополнение к этому, функция для извлечения этого элемента взяла и удалила его оттуда. Это поведение очень неожиданно для подобной функции. Оно противоречит большому количеству принципов построения хорошего кода (например "Command–query separation", этот принцип рассматривается в курсе по функциям). Правильная реализация данной функции выглядит так:
В каких же случаях стоит менять сам список? Есть ровно одна причина, по которой так делают – производительность. Именно поэтому некоторые встроенные методы списков меняют их, например reverse() или sort():
Копирование списков
У списка нет встроенных методов или функций, которые его изменяют, но возвращают новый, не трогая старый. Чтобы изменить список, не затрагивая изначальный, его нужно скопировать.
Несмотря на то, что подход, меняющий списки напрямую, сложнее в отладке, его используют в некоторых языках для увеличения эффективности работы. Если список достаточно большой, то полное копирование окажется дорогой операцией. В реальной жизни (веб-разработчика) это почти никогда не является проблемой, но знать об этом полезно.
Выводы
Все типы данных в Python передаются по ссылкам. Списки содержат коллекцию не значений, а ссылок.
При передаче списка в функцию передается ссылка на него. Изменение списка внутри функции повлияет на оригинал. Предпочтительнее возвращать новый список из функции, чтобы избежать неожиданных изменений.







