В Python все является объектом. В основном это побочный эффект принципа дизайна Гвидо Ван Россума (создателя языка программирования Python) «все первоклассно». Первоклассное все означает, что все является экземпляром чего-то другого. В более общем плане это означает, что все находится на том же «уровне», что и все остальное. Взгляните на следующее (все запускается в интерпретаторе python3):

Даже типы данных являются объектами своих соответствующих классов. Итак, если типы данных являются просто экземплярами классов, мы можем представить, что разные типы данных имеют разные атрибуты. Например, списки являются изменяемыми типами данных, в то время как кортежи и встроенные типы данных, такие как int и float, неизменяемы. Обратите внимание на следующее:

В первом кадре мы создаем две одинаковые струны. Поскольку строки являются неизменяемыми объектами (вы не можете изменить строку), Python будет экономить использование памяти, если обе переменные будут указывать на один и тот же объект. Мы видим, что это правда, потому что строка1 и строка2 имеют один и тот же адрес памяти (мы можем использовать id () для проверки адреса памяти объекта). Однако во втором кадре мы пытаемся изменить значение строки1 с помощью «+ =‘ e ’». Поскольку строки неизменяемы, Python вынужден создать новый строковый объект, указывающий на строку «cate». Обратите внимание на то же поведение с целыми числами:

Хотя кажется, что целые числа действуют одинаково, на самом деле это происходит по другой причине. Чтобы сэкономить память, CPython заранее выделяет (или связывает) первые 262 целых числа при запуске. Это означает, что числа от -5 до 256 (включительно) автоматически привязываются к определенным адресам в памяти. Вот почему в примере выше a и b ссылаются на одно и то же место в памяти. Обратите внимание на следующее:

С технической точки зрения CPython создал макросы NSMALLPOSINTS и NSMALLNEGINTS. Эти макросы относятся к -5 и 256 соответственно. CPython фактически хранит ссылки на все эти целочисленные объекты в массиве. Когда мы «создаем» целое число в этом диапазоне (пример: a = 5), мы фактически просто указываем нашей переменной, чтобы она указывала на адрес, хранящийся в этом массиве. Массив настроен на эту конкретную область, потому что это наиболее часто используемые числа. Как только вы запросите номер вне этого диапазона, CPython будет вынужден начать поиск новых мест в памяти, где он может хранить числа.

Конечно, если мы хотим, чтобы a и b указывали на один и тот же объект (даже если они находятся за пределами диапазона наших макросов), мы можем передать ссылку на память одной переменной другой:

Этот последний пример демонстрирует механизм, известный как «псевдоним». Поскольку a и b теперь указывают на один и тот же объект, мы можем сказать, что объект имеет псевдоним, или что a является псевдонимом для b, и наоборот.

Поскольку целые числа являются неизменяемыми объектами, когда мы изменяем значение a во втором кадре, мы заставляем Python создать новый объект. Эти последние два примера демонстрируют умный подход к переменным в Python. Переменные в Python - это, по сути, имена, которые мы используем для обозначения объектов. Они похожи на ярлыки. Это ясно из приведенного выше целочисленного примера, потому что даже после того, как мы скажем «a = 4» (скажем, чтобы он указывал на совершенно другой объект), мы все еще используем имя или «метку» для ссылки на новый объект.

Теперь мы немного знаем о том, как Python обрабатывает неизменяемые объекты. Давайте изучим изменяемые объекты.

Списки - это изменяемые объекты. Давайте инициализируем два списка:

Интересно, что Python не пытается экономить и заставляет списки a и список b указывать на один и тот же объект, даже если они содержат одни и те же данные. Список a и список b - разные объекты. Поскольку списки изменяемы, Python знает, что вы, вероятно, будете добавлять или удалять элементы из этих списков.

Теперь, когда мы понимаем некоторые важные свойства изменяемых и неизменяемых объектов, давайте узнаем о некоторых операторах, которые мы можем использовать для проверки состояния объектов.

В приведенном выше примере мы используем оператор «==», чтобы проверить, указывают ли наши переменные на объект, содержащий те же данные. Поскольку оба наших списка содержат одни и те же данные, это оценивается как «Истина».

В приведенном выше примере мы используем оператор «is», чтобы проверить, указывают ли наши переменные на один и тот же объект. Поскольку a и be указывают на разные объекты, это оценивается как «False».

Чтобы получить наглядное представление, мы можем представить наш вышеописанный сценарий с помощью следующей диаграммы. Изображение слева, где a и b указывают на отдельные объекты, содержащие строку «банан», представляет ситуацию, которую мы создали выше.

Чтобы получить изображение справа, где a и b указывают на один и тот же объект, нам нужно сделать следующее:

Теперь мы устанавливаем оба списка так, чтобы они указывали на один и тот же объект. Мы проверили это на второй диаграмме, где мы напечатали адрес, на который указывает каждая переменная. Этот пример также затрагивает разницу между присваиванием и ссылками в Python. Мы можем рассматривать переменные как имена или ссылки на определенные места в памяти. Эти ячейки памяти содержат объекты (значения). Например, выражение a = 5 сообщает или «связывает» имя «a», чтобы оно указывало на определенное место в памяти, которое содержит значение 5. Мы можем «повторно привязать» переменную, указав ей ссылаться на другое место в памяти - a = 6.

Теперь давайте кратко рассмотрим кортежи. Кортежи - это неизменяемые структуры данных, однако они могут содержать внутри себя изменяемые объекты, например списки. Хотя вы не можете добавлять или добавлять новые элементы в кортеж после его создания, вы можете изменить изменяемые объекты, хранящиеся внутри кортежа. Обратите внимание на следующее:

В этом примере мы создаем кортеж («дум») со списком внутри него. Затем мы назначаем объект списка, хранящийся в кортеже, другой переменной dee. Затем мы проверяем, что объект кортежа и объект списка, находящиеся внутри кортежа, находятся по разным адресам памяти. Затем мы пытаемся изменить список, хранящийся внутри кортежа. Мы проверяем, что список был изменен как на «dee», так и на «dum». Кортежи - это уникальные экземпляры неизменяемых объектов, которые могут содержать изменяемые объекты. Вы можете изменить эти изменяемые объекты.

Последняя уникальная структура данных в Python - это замороженный набор. Замороженные наборы - это неизменяемые версии объектов-наборов. Хотя элементы можно добавлять и удалять из обычного набора Python:

Никакие элементы не могут быть добавлены или удалены из замороженного набора:

Так почему это так важно? Почему нам нужно заботиться о том, как Python обрабатывает изменяемые и неизменяемые объекты?

Прежде всего, вам следует заботиться, потому что, если вы хотите быть опытным программистом, вы должны всегда точно знать, что происходит с вашими переменными.

Во-вторых, это важно, потому что способ передачи объектов в функции Python зависит от изменчивости этих объектов. Если вы передаете неизменяемые объекты в функцию, передача действует как «вызов по значению». Обратите внимание на следующее:

В первом кадре мы определяем функцию, которая увеличивает и печатает целочисленную переменную. Мы говорим функции печатать идентификаторы объекта для целей отладки. Во втором кадре мы вызываем функцию. Первый напечатанный идентификатор совпадает с идентификатором целочисленного объекта b, напечатанного вне функции. Это говорит нам о том, что объект b был передан по ссылке в функцию increment (). Однако второй идентификатор, который мы печатаем после приращения, отличается. Как только мы переходим на шаг увеличения и понимаем, что мы должны изменить значение нашего объекта, Python создает другой целочисленный объект со значением из старого объекта и увеличивает этот объект. Вот почему значение объекта в нашей функции равно 6, а значение нашего объекта вне функции равно 5.

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

Из первого оператора id мы видим, что наш объект был передан по ссылке в нашу функцию, как и в прошлый раз. Однако, в отличие от прошлого раза, адрес, напечатанный нашим вторым методом id (который выполняется после append ()), соответствует тому же объекту. Поскольку списки являются изменяемыми структурами данных, Python может просто добавить элемент в существующий список вместо создания совершенно нового объекта. Мы также можем видеть, что обновленный список сохраняется за пределами области нашей функции.

Вот почему люди говорят, что Python передает не по значению или ссылке, а по «вызову по ссылке на объект».

В заключение, изменяемые объекты в Python включают в себя: список, словарь, набор и массив байтов. К неизменяемым объектам относятся: int, float, complex, tuple, string, замороженный набор, байты. Надеюсь, вы чувствуете себя более уверенно в различных способах обработки Python изменяемых и неизменяемых объектов.