Сравнение и идентичность двух списков в Python
Если нужен список уникальных элементов в объединении двух списков:
result = list(set(lst1 + lst2)) # ['red', 'white', 'green', 'blue']
Симметричная разность двух списков:
result = list(set(lst1) ^ set(lst2)) # ['green', 'blue']
Обычная разность(множество из lst1 не входящее в lst2 ):
result = list(set(lst1) - set(lst2)) # ['green','blue']
Вариант, сохраняющий порядок с меньшим количеством конверсий типов:
result = [x for x in lst1 if x in set(lst2))]
Как сравнить поиндексно значения двух списков?
Имеем два списка:
lit1 = [23, 56, 78, 69] lit2 = [11, 89, 33, 45]
Задача состоит в том, чтобы сравнить эти списки поиндексно, при сравнении меньший элемент вынести в отдельный список result .
Смотрим решение с комментариями к коду:
list1 = [23, 56, 78, 69] list2 = [11, 89, 33, 45] # список для результата result = [] # перебираем элементы списка # в пределах длины первого списка for i in range(len(lst1)): # сравниваем элементы первого списка # с элементами второго списка if lst1[i] lst2[i]: # добавляем в итоговый список `result` # элемент из первого, если он меньше result.append(list1[i]) else: # иначе - добавляем элемент из второго списка result.append(list2[i]) # выводим итоговый список print(result)
Код выше можно записать короче, при помощи выражения-генератора списка или функции map()
result = [min(p) for p in zip(list1, list2)] # или result = list(map(min, zip(l1, l2)))
Вычисление идентичности двух списков в Python.
Задача состоит в том, чтобы проверить, содержат ли два списка точно одинаковые элементы. Это довольно полезная распространенная задача.
Так как оператор == сравнивает списки поиндексно слева направо, то решение состоит в том, чтобы перед сравнением эти списки отсортировать, ведь заранее неизвестно, упорядочены они или нет.
Метод списка list.sort() сортирует его на месте (т.е. не создает новый). Если необходимо сохранить начальную сортировку списков, то для сортировки используйте функцию sorted() , она создаст новый отсортированный список, который можно сравнивать.
lst1 = [5, 3, 1, 2, 4] lst2 = [1, 2, 4, 3, 5] if lst1.sort() == lst2.sort(): print('Списки идентичны') else: print('Списки не идентичны')
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Список Python как аргумент по умолчанию
- Использование списка в качестве стека
- Использование списка в качестве очереди
- Генератор списка list
- Эффективное использование генераторов списков
- Операция присваивания на месте и списки
- Поведение списков Python в разных областях видимости
- Сравнение и идентичность двух списков
- Как получить несколько последних элементов списка
Объекты должны быть неизменными
В объектно-ориентированном программировании объект является неизменным, если его состояние не может быть изменено после его создания.
В Java хорошим примером неизменяемого объекта является String . После создания мы не можем изменить его состояние. Мы можем запросить, чтобы он создавал новые строки, но его собственное состояние никогда не изменится.
Однако в JDK не так много неизменных классов. Взять, к примеру, класс Date . Можно изменить его состояние с помощью setTime() .
Я не знаю, почему дизайнеры JDK решили сделать эти два очень похожих класса по-разному. Тем не менее, я считаю, что дизайн изменяемой Date имеет много недостатков, в то время как неизменяемая String в большей степени соответствует духу объектно-ориентированной парадигмы.
Более того, я считаю, что все классы должны быть неизменными в идеальном объектно-ориентированном мире . К сожалению, иногда это технически невозможно из-за ограничений в JVM. Тем не менее, мы всегда должны стремиться к лучшему.
Это неполный список аргументов в пользу неизменности:
- неизменяемые объекты проще создавать, тестировать и использовать
- действительно неизменяемые объекты всегда поточно-ориентированы
- они помогают избежать временной связи
- их использование не имеет побочных эффектов (без защитных копий)
- проблема изменчивости идентичности избегается
- у них всегда есть отказ атомности
- их гораздо проще кешировать
- они предотвращают пустые ссылки, которые являются плохими
Давайте обсудим наиболее важные аргументы один за другим.
Поток безопасности
Первый и самый очевидный аргумент заключается в том, что неизменяемые объекты являются потокобезопасными. Это означает, что несколько потоков могут обращаться к одному и тому же объекту одновременно, не конфликтуя с другим потоком.
Если никакие объектные методы не могут изменить его состояние, независимо от того, сколько из них и как часто они называются параллельными, они будут работать в своем собственном пространстве памяти в стеке.
Гетц и соавт. объяснил преимущества неизменяемых объектов более подробно в их очень известной книге Java Concurrency in Practice (настоятельно рекомендуется).
Избежание временного соединения
Вот пример временной привязки (код выполняет два последовательных HTTP-запроса POST, где второй содержит HTTP-тело):
Python 3: изменяемый, неизменяемый…

Многие из нас считают Python прекрасным языком программирования. В нем легко разобраться, он очень хорошо читается, а код на нем легко поддерживать. Но основная причина простоты этого языка состоит в том, что под капотом у него есть очень много всего. И хотя многие люди просто знают, что в языке есть и сложные вещи, и им этого достаточно, понимание тонкостей поможет вам избежать появления многих багов в ваших программах. Цель этой статьи — познакомить вас с этими тонкостями.
Объекты
Давайте начнем с краеугольного камня Python — с объектов. Объектом является буквально все. Ваш модуль это объект, функция — объект, число — тоже объект. Объекты это просто экземпляры классов.
Чтобы понять эту концепцию, мы можем представить, что объекты — это отдельные люди, а классы — это группы, к которым эти люди принадлежат. Скажем, все мы — люди (класс human), но я — уникальный индивид (экземпляр класса human).
В Python все является объектом. Каждый объект имеет собственный тип данных и внутреннее состояние (данные). Давайте для начала уясним, как данные хранятся в памяти. Для этого рассмотрим пару примеров.
Пример 1
Когда Python выполняет a = 1000 , это создает числовой объект где-то в памяти (допустим, по адресу 0x1111), где сохраняется значение 1000. При этом a не хранит значение 1000, она хранит ссылку на этот объект (адрес в памяти). Когда выполняется следующее предложение a = 1500 , в памяти (допустим, по адресу 0x2222) создается другой объект со значением 1500, и теперь a хранит ссылку на этот новый объект.
a = 1000 a = 1500

Пример 2
Когда Python выполняет a = 2000 , как объяснялось в предыдущем примере, создается числовой объект в памяти по адресу 0x3333, и переменная a указывает на этот объект. После выполнения b = a переменная b тоже начинает указывать на тот же адрес в памяти, что и a . Обратите внимание, что не значение a копируется в b , а ссылка (адрес в памяти).
a = 2000 b = a

Идентичность и тип
Прежде чем мы погрузимся в пучину объектов в Python, давайте рассмотрим, что стоит за двумя встроенными функциями: id() и type() . Эти две функции способны предоставить нам критически важную информацию о переменной.
Функция type() вернет тип данных объекта, который чаще всего совпадает с классом, к которому принадлежит этот объект. Например, если у меня есть список l , выполнение type(l) вернет класс list:

Функция id() возвращает идентификатор указанной вами переменной. Каждая переменная в Python ссылается на какой-нибудь объект, а идентификатор переменной это целое число, «привязанное» к конкретному объекту. В реализации CPython это число — адрес объекта в памяти. Идентификатор объекта позволяет разграничить случаи, когда переменные идентичны, и когда они ссылаются на один и тот же объект. Для определения идентичности мы можем использовать «==», а «is» используется для определения того, указывают ли переменные на один и тот же объект.
Скажем, у нас есть два списка, x и y . Мы можем видеть, что у них разные идентификаторы:


Но все станет куда более странно, если мы сделаем вот так:

Вероятно, вы думаете: «Погодите, погодите! Что?! Ведь пару секунд назад, когда мы рассматривали списки, я понял, что они разные. А эти тогда почему одинаковые?» Чтобы ответить на этот вопрос, мы должны углубиться в предмет изменяемости.
Изменяемые объекты
Итак, что же такое изменяемость (мутабельность)? Выражаясь простым языком, это способность мутировать — или изменять объект. К изменяемым (мутабельным) типам в Python относятся списки, множества, словари и bytearrays (массивы байтов). Давайте рассмотрим пример с нашим старым добрым списком. Мы можем добавлять в него элементы:

Мы можем удалять из него элементы:

Как уже говорилось, в Python все является объектом. Поэтому каждый элемент в списке это тоже объект. Если мы приглядимся, все опять станет очень странным:

Почему идентификатор l[1] изменился? Идентификатор всего списка остался неизменным, хотя мы изменили его значения, но почему же изменился идентификатор его элемента? Потому что числа и строки неизменяемы (не мутабельны).
Неизменяемые объекты
Неизменяемость это противоположность изменяемости (логично). Если объект неизменяемый, это значит, что вы не можете менять его содержимое. К встроенным неизменяемым типам относятся целые числа, числа с плавающей точкой, комплексные числа, строки, кортежи, frozenset-ы и байты.
В приведенном выше примере объект список изначально содержал ссылки на объекты «1», «2» и «3». Хотя мы не можем изменить сам объект (цифру 2), можно изменить список за счет изменения хранящейся в нем ссылки. Теперь список содержит ссылку на объект «Hello». Другими словами, произошло переназначение. И хотя увеличивать неизменяемый объект нельзя, мы можем сделать так:
Кажется, что мы добавили «World» к «Hello», но на самом деле произошло примерно следующее:
s = s + “ World”
Было вычислено значение s («Hello») плюс “ World”, и переменная s получила ссылку на новый объект. Похожий процесс происходит при добавлении чисел:

Но если вы попытаетесь изменить элемент в неизменяемом наборе, интерпретатор возбудит исключение:

Аналогично — хотя удалить элемент в неизменяемом объекте нельзя, вы можете взять срез объекта, в результате чего вернется новый объект, содержащий нужные значения:

Поскольку Python должен создавать отдельный объект для каждого уникального неизменяемого значения (что занимает много памяти), интерпретатор разумным образом оптимизирует создание объектов. Эта оптимизация происходит за счет использования одинаковых ссылок для неизменяемых объектов, таких как строки:

В CPython все еще круче: для облегчения доступа все общие маленькие int-объекты (от -5 (включительно) до 257) сохраняются в массиве. Другие объекты создаются при необходимости и удаляются, когда не остается переменных, ссылающихся на них, а эти маленькие int-объекты сохраняются в течение всей работы программы.
Преаллокация в Python
А теперь домашнее задание для вас:
- Создайте две переменные со значениями между -5 и 256, а затем проверьте, указывают ли они на один и тот же объект.
- Сделайте то же самое, но используйте значения вне указанного диапазона.
При запуске Python3 сохраняет массив числовых объектов от -5 до 256 (включительно). Например, для объекта int используются макросы NSMALLPOSINTS и NSMALLNEGINTS . Давайте посмотрим исходный код:
#ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* References to small integers are saved in this array so that they can be shared. The integers that are saved are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; #endif #ifdef COUNT_ALLOCS Py_ssize_t quick_int_allocs; Py_ssize_t quick_neg_int_allocs; #endif
Что это означает? Это значит, что когда вы создаете int из диапазона от -5 до 256, вы на самом деле ссылаетесь на уже существующий объект. Это сделано для того, чтобы избежать многократного создания часто используемых объектов, а кроме того таким образом вы можете представить любой символ ASCII.
Почему это важно?
При присваивании значений переменным очень важно понимать разницу между изменяемыми и неизменяемыми объектами. Изменяемые объекты имеют пару тузов в рукаве. Мы уже рассматривали этот пример:


Почему y — тот же объект, что x ? Ведь в первом примере они были разными объектами! В Python большое значение имеет то, как происходит присваивание. В этом случае мы не назначили переменной y другой объект. Вместо этого мы создали псевдоним для x . То есть сделали так, что переменная y получила ту же ссылку на объект, которая была у переменной x . Поскольку обе переменные теперь указывают на один объект, изменение одной переменной повлечет за собой изменение другой.

А что, если мы хотим создать копию объекта, чтобы при внесении изменений не волноваться о том, что это затронет оригинал? Мы можем взять срез списка, при этом вернется новый объект (так же, как когда мы брали срез неизменяемого объекта):

Добавление элементов в списки тоже может быть хитрой штукой. В Python то, что находится в левой части выражения присваивания, получает ссылку на все, что вычисляется в правой. Поэтому:

Правая сторона выражения была вычислена, результат был определен как [1, 2, 3, 4, 5], а затем был создан новый объект, ссылку на который получила переменная l . Но если мы сделаем так:

Мы видим, что объект остался прежним. Это замещающее присваивание — эквивалент добавления элементов в список. Но поскольку иммутабельные объекты не могут быть изменены, выражения a += b и a = a + b будут работать одинаково и аналогично нашему примеру «Hello World».
Передача аргументов в функции
В других языках программирования переменные часто передаются одним из двух способов:
- по значению (новая переменная получает то же значение, что и переданный аргумент)
- по ссылке (переменная хранит ссылку на данные, при этом данные могут быть изменены).
Но Python в этом плане уникален. Python передает ссылку на объект. Аналогично тому как мы делали переменную y псевдонимом переменной x ( x = y ), при передаче переменной в функцию аргумент функции становится псевдонимом для ссылки на объект.
Это означает, что если внутри функции произойдет новое присваивание, это не изменит оригинальную переменную, переданную в функцию. Поскольку иммутабельные типы не могут быть изменены, никакое изменение переменной внутри функции не сохранится:

Но поскольку списки мутабельны, их содержимое изменять можно:

Обратите внимание, что ссылка на список остается той же на протяжении всей программы. Тем не менее, если бы мы переприсвоили значение l внутри функции, это не затронуло бы исходный список.

Исключения в неизменяемости
Не все неизменяемые объекты на самом деле неизменяемы. Да, все очень запутанно, но сейчас разберемся.
Как уже говорилось, в Python контейнеры типа кортежей неизменяемы. Это значит, что значение tuple не может меняться после того, как кортеж создан. Но «значение» кортежа на самом деле является последовательностью имен с неизменяемыми привязками к объектам. Главное, что нужно понять, это что неизменяемы именно привязки, а не сами объекты.
Давайте посмотрим на кортеж t = (‘holberton’, [1, 2, 3]) .
Кортеж t содержит элементы с разными типами данных. Первый элемент — неизменяемая строка, а второй — изменяемый список. Сам кортеж неизменяемый (нет никаких методов для изменения его содержимого). Аналогично и строка неизменяема, потому что у строк нет никаких методов для их изменения. Но объект список имеет методы, с помощью которых его можно изменить, так что он изменяемый. Это мелочь, но важная: «значение» неизменяемого объекта не может меняться, но объекты, из которых он состоит, — могут.
Заключение
Понимание тонкостей языка может быть чрезвычайно полезным. Многие программисты не уделяют достаточно времени тому, чтобы хорошенько изучить языки, которыми пользуются на постоянной основе. Но близкое знакомство с Python — отличный способ избежать багов.
Кроме того, самодисциплина, позволяющая вам сесть и изучить, что происходит под капотом языка, — сама по себе отличная вещь, которая сослужит вам прекрасную службу в вашей программистской карьере.
Надеемся, эта статья была вам полезной!
Упущенные из виду факты о переменных и объектах в Python: все дело в указателях

В Python переменные и структуры данных не содержат объектов. Этот факт часто упускается из виду, и его трудно уяснить.
Вы можете успешно использовать Python годами, не вникая в нижеприведенные концепции, но полученные здесь знания, безусловно, облегчат решение многих из распространенных проблем Python.
- Терминология
- Переменные в Python являются указателями, а не бакетами
- Присвоения указывают переменную на объект
- Два типа «изменений» в Python
- Равенство сравнивает объекты, а тождество сравнивает указатели
- Нет исключений для неизменяемых объектов
- Структуры данных содержат указатели
- Аргументы функций действуют как операторы присваивания
- Копии поверхностны, и обычно это считается нормальным.
- Резюме
Терминология
Давайте начнем с представления некоторой терминологии. Последние несколько определений, вероятно, сейчас не будут иметь смысла, пока мы не сформулируем их более подробно позже.
Объект (он же значение): «вещь». Списки, словари, строки, числа, кортежи, функции и модули — все это объекты. Понятие «объект» не поддается определению, потому что в Python все является объектом.
Переменная (она же имя): имя, используемое для ссылки на объект.
Указатель (он же ссылка): описывает, где находится объект (часто изображается визуально в виде стрелки).
Равенство: представляют ли два объекта одни и те же данные.
Идентичность: ссылаются ли два указателя на один и тот же объект.
Все эти термины лучше всего можно понять по их взаимосвязи друг с другом, и это основная цель данной статьи.
Переменные в Python — это указатели, а не бакеты
Переменные в Python — это не бакеты, содержащие вещи; это указатели (они указывают на объекты).
Слово «указатель» может звучать пугающе, но большая часть этого страха происходит от взаимосвязанных понятий (например, разыменование), которые не имеют отношения к Python. В Python указатель просто представляет собой связь между переменной и объектом.
Представьте себе, что переменные живут в стране переменных, а объекты — в стране объектов. Указатель — это маленькая стрелка, которая соединяет каждую переменную с объектом, на который она указывает.

Приведенная выше диаграмма представляет состояние нашего процесса Python после выполнения данного кода:
>>> numbers = [2, 1, 3, 4, 7] >>> numbers2 = [11, 18, 29] >>> name = "Trey"
Если слово указатель вас пугает, используйте вместо него слово ссылка. Всякий раз, когда вы видите в этой статье фразы, основанные на понятии указателя, мысленно переведите их как фразы, основанные на ссылке:
- указатель ⇒ ссылка
- указать на ⇒ ссылаться на
- указал на ⇒ сослался
- указывать X на Y ⇒ заставлять X ссылаться на Y
Присваивания указывают переменную на объект
Операторы присваивания указывают переменную на объект. Вот и все.
Если мы запустим этот код:
>>> numbers = [2, 1, 3, 4, 7] >>> numbers2 = numbers >>> name = "Trey"
Состояние наших переменных и объектов будет выглядеть следующим образом:

Обратите внимание, что numbers и numbers2 указывают на один и тот же объект. Если мы изменим этот объект, то обе переменные будут как бы «видеть» данную перемену:
>>> numbers.pop() 7 >>> numbers [2, 1, 3, 4] >>> numbers2 [2, 1, 3, 4]
Вся эта странность была связана с данным оператором присваивания:
>>> numbers2 = numbers
Операторы присваивания ничего не копируют: они просто указывают переменную на объект. Таким образом, присвоение одной переменной другой переменной просто указывает две переменные на один и тот же объект.
Два типа «изменения» в Python
В Python есть два различных типа «изменения»:
- Присваивание изменяет переменную (изменяет объект, на который она указывает).
- Мутация изменяет объект (на который может указывать любое количество переменных).
Слово «изменение» зачастую носит двусмысленный характер. Фраза «мы изменили x» может означать «мы переназначили x», а может означать «мы мутировали объект, на который указывает x».
Мутации изменяют объекты, а не переменные. Но переменные указывают на объекты. Поэтому если другая переменная указывает на объект, который мы только что мутировали, эта переменная отразит то же самое изменение; не потому, что изменилась переменная, а потому, что изменился объект, на который она указывает.
Равенство сравнивает объекты, а тождество сравнивает указатели
Оператор == в Python проверяет, что два объекта представляют одни и те же данные (так называемое равенство):
>>> my_numbers = [2, 1, 3, 4] >>> your_numbers = [2, 1, 3, 4] >>> my_numbers == your_numbers True
Оператор is в Python проверяет, являются ли два объекта одинаковыми (также известный как идентичность):
>>> my_numbers is your_numbers False
Переменные my_numbers и your_numbers указывают на объекты, представляющие одни и те же данные, но они не являются одним и тем же объектом.
Поэтому изменение одного объекта не приводит к изменению другого:
>>> my_numbers[0] = 7 >>> my_numbers == your_numbers False
Если две переменные указывают на один и тот же объект:
>>> my_numbers_again = my_numbers >>> my_numbers is my_numbers_again True
Изменение объекта, на который указывает одна переменная, также изменяет объект, на который указывает другая, потому что они обе указывают на один и тот же объект:
>>> my_numbers_again.append(7) >>> my_numbers_again [2, 1, 3, 4, 7] >>> my_numbers [2, 1, 3, 4, 7]
Оператор == проверяет равенство, а оператор is проверяет тождество. Это различие между идентичностью (тождественностью) и равенством существует потому, что переменные не содержат объектов, они на них указывают.
В Python проверки на равенство встречаются очень часто, а проверки на идентичность — крайне редки.
Нет исключения для неизменяемых (иммутабельных) объектов
Но подождите, модификация числа не изменяет другие переменные, указывающие на то же число, верно?
>>> n = 3 >>> m = n # n and m point to the same number >>> n += 2 >>> n # n has changed 5 >>> m # but m hasn't changed! 3
В Python изменение числа невозможно. Числа и строки являются иммутабельными, то есть их нельзя изменить. Вы не можете изменить иммутабельный объект.
Так что насчет оператора += выше? Разве он не изменил число? (Не изменил.)
При работе с иммутабельными объектами эти два оператора эквивалентны:
>>> n += 2 >>> n = n + 2
Для иммутабельных объектов дополненные присваивания ( += , *= , %= и т.д.) выполняют операцию (которая возвращает новый объект), а затем выполняют присваивание (этому новому объекту).
Любая операция, которая, как вам кажется, изменяет строку или число, вместо этого возвращает новый объект. Любая операция над иммутабельным объектом всегда возвращает новый объект, а не изменяет исходный.
Структуры данных содержат указатели
Как и переменные, структуры данных не содержат объектов, они содержат указатели на объекты.
Допустим, мы создаем список, содержащий списки в качестве элементов (список списков):
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Затем мы создаем переменную, указывающую на второй список в нашем списке списков:
>>> row = matrix[1] >>> row [4, 5, 6]
Состояние наших переменных и объектов теперь выглядит следующим образом:

Наша переменная row указывает на тот же объект, что и индекс 1 в списке нашей matrix :
>>> row is matrix[1] True
Поэтому, если мы изменим список, на который указывает row :
>>> row[0] = 1000
Мы увидим это изменение в обоих местах:
>>> row [1000, 5, 6] >>> matrix [[1, 2, 3], [1000, 5, 6], [7, 8, 9]]
Принято говорить о структурах данных, «содержащих» объекты, но на самом деле они содержат только указатели на объекты.
Аргументы функций действуют как операторы присваивания
Вызовы функций также выполняют присваивание.
Если вы мутируете объект, который был передан в вашу функцию, вы также мутировали исходный объект:
>>> def smallest_n(items, n): . items.sort() # This mutates the list (it sorts in-place) . return items[:n] . >>> numbers = [29, 7, 1, 4, 11, 18, 2] >>> smallest_n(numbers, 4) [1, 2, 4, 7] >>> numbers [1, 2, 4, 7, 11, 18, 29]
Но если вы переназначите переменную другому объекту, исходный объект не изменится:
>>> def smallest_n(items, n): . items = sorted(items) # this makes a new list (original is unchanged) . return items[:n] . >>> numbers = [29, 7, 1, 4, 11, 18, 2] >>> smallest_n(numbers, 4) [1, 2, 4, 7] >>> numbers [29, 7, 1, 4, 11, 18, 2]
Здесь мы переназначаем переменную items . Данное переназначение изменяет объект, на который указывает переменная items , но при этом не изменяет исходный объект.
В первом случае мы изменили объект, а во втором — изменили переменную.
Вот еще один пример, с которым можно иногда столкнуться:
class Widget: def __init__(self, attrs=(), choices=()): self.attrs = list(attrs) self.choices = list(choices)
Методы инициализатора класса часто копируют переданные им итерируемые объекты, создавая новый список из их элементов. Это позволяет классу принимать любые итерируемые объекты (а не только списки) и отделяет исходный итерируемый объект от класса (таким образом модификация этих списков не помешает оригинальному вызывающему объекту). Приведенный выше пример был позаимствован из Django.
Не мутируйте объекты, переданные в вашу функцию, если этого не ожидает вызывающая функция.
Копии поверхностны, и это обычно нормально
Нужно скопировать список в Python?
>>> numbers = [2000, 1000, 3000]
Вы можете вызвать метод copy (если уверены, что ваш итерируемый объект является списком):
>>> my_numbers = numbers.copy()
Или можете передать его в конструктор list (это работает с любым итерируемым объектом):
>>> my_numbers = list(numbers)
Оба этих метода создают новый список, который указывает на те же объекты, что и исходный список.
Два списка различны, но объекты в них одинаковы:
>>> numbers is my_numbers False >>> numbers[0] is my_numbers[0] True
Поскольку целые числа (и все числа) в Python иммутабельны, нам не важно, что каждый список содержит одни и те же объекты, поскольку мы все равно не можем их мутировать.
С мутабельными объектами данное различие имеет значение. В этом случае получается два списка списков, каждый из которых содержит указатели на одни и те же три списка:
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] >>> new_matrix = list(matrix)
Эти два списка не одинаковы, но каждый элемент в них один и тот же:
>>> matrix is new_matrix False >>> matrix[0] is new_matrix[0] True
Вот довольно сложное визуальное представление этих двух объектов и содержащихся в них указателей:

Таким образом, если мы мутируем первый элемент в одном списке, это приведет к мутации того же элемента в другом списке:
>>> matrix[0].append(100) >>> matrix [[1, 2, 3, 100], [4, 5, 6], [7, 8, 9]] >>> new_matrix [[1, 2, 3, 100], [4, 5, 6], [7, 8, 9]]
Когда вы копируете объект в Python, если он указывает на другие объекты, то копируются указатели на них, а не копируются сами объекты.
Начинающие программисты Python, в связи с таким поведением, добавляют в свой код функцию copy.deepcopy . Функция deepcopy пытается рекурсивно скопировать объект вместе со всеми объектами, на которые он указывает.
Иногда новички в Python используют deepcopy для рекурсивного копирования структур данных:
from copy import deepcopy from datetime import datetime tweet_data = [, ] # Parse date strings into datetime objects processed_data = deepcopy(tweet_data) for tweet in processed_data: tweet["date"] = datetime.strptime(tweet["date"], "%b %d %Y")
Но в Python мы обычно предпочитаем создавать новые объекты вместо того, чтобы мутировать существующие. Поэтому мы можем полностью исключить использование deepcopy , создав еще один список новых словарей вместо глубокого копирования нашего старого списка словарей.
# Parse date strings into datetime objects processed_data = [ <**tweet, "date": datetime.strptime(tweet["date"], "%b %d %Y")>for tweet in tweet_data ]
В Python мы отдаем предпочтение поверхностным копиям. Если вы не мутируете объекты, которые вам не принадлежат, у вас обычно нет необходимости в использовании deepcopy .
Функция deepcopy , конечно, применяется, но зачастую в ней нет необходимости. «Как избежать использования deepcopy » заслуживает отдельного обсуждения в одной из последующих статей.
Резюме
Переменные в Python — это не бакеты, содержащие вещи; это указатели (они указывают на объекты).
Модель переменных и объектов в Python сводится к двум основным правилам:
- Мутация изменяет объект.
- Присваивание указывает переменную на объект.
А также с вытекающими из этого правилами:
- Переназначение переменной указывает на другой объект, оставляя исходный объект неизменным.
- Присваивания ничего не копируют, поэтому копировать объекты нужно по мере необходимости.
Более того, структуры данных работают точно так же: списки и словари содержат указатели на объекты, а не сами объекты. Аналогично работают и атрибуты: атрибуты указывают на объекты (точно так же, как любая переменная указывает на объект). Таким образом, объекты не могут содержать объекты в Python (они могут только указывать на них).
Обратите внимание, что хотя мутации изменяют объекты (а не переменные), несколько переменных могут указывать на один и тот же объект. Если две переменные указывают на один и тот же объект, изменения этого объекта будут видны при обращении к любой из этих переменных (поскольку они обе указывают на один и тот же объект).
Подробнее об этой теме смотрите:
- Серия моих скринкастов по присвоениям и мутации в Python
- Лекция Ned Batchelder’s “Имена и значения в Python”
- Доклад Brandon Rhodes «Имена, объекты и падение с обрыва».
Данная ментальная модель Python сложна для усвоения, поэтому ничего страшного, если она все еще кажется запутанной! Возможности и лучшие практики Python часто автоматически подталкивают нас к тому, чтобы «делать правильные вещи». Но если ваш код ведет себя странно, это может быть связано с изменением объекта, который вы не собирались менять.
Всех, дочитавших статью до конца, приглашаем на открытое занятие «Tabula rasa Python проекта». На занятии рассмотрим best practices по настройке окружения для разработки свежего проекта на Python, а также поговорим про всевозможные инструменты и автоматизации, которые могут применяться в таком случае. Регистрация на вебинар.
- Блог компании OTUS
- Python