Марк Лутц - Изучаем Python, Четвертое издание (1184811), страница 53
Текст из файла (страница 53)
В результате схема взаимоотношений приобретает вид, показанный на рис. 6.3.ИменаСсылкиОбъекты3a=3b=aa=‘spam’ab‘spam’Рис. 6.3. Имена и объекты после выполнения инструкции присваивания a =‘spam’. Переменная a ссылается на новый объект (то есть на область памяти), созданный в результате выполнения литерального выражения ‘spam’,но переменная b по-прежнему ссылается на первый объект 3. Так как этаоперация присваивания никак не изменяет объект 3, она изменяет толькопеременную a, но не bТо же самое произошло бы, если бы ссылка на объект ‘spam’ вместо переменнойa была присвоена переменной b – изменилась бы только переменная b, но не a.Аналогичная ситуация возникает, даже если тип объекта не изменяется.
Например, рассмотрим следующие три инструкции:Разделяемые ссылки201>>> a = 3>>> b = a>>> a = a + 2В этой последовательности происходят те же самые события: интерпретаторPython создает переменную a и записывает в нее ссылку на объект 3. После этого он создает переменную b и записывает в нее ту же ссылку, что хранится в переменной a, как показано на рис. 6.2. Наконец, последняя инструкция создаетсовершенно новый объект (в данном случае – целое число 5, которое являетсярезультатом выполнения операции сложения).
Это не приводит к изменениюпеременной b. В действительности нет никакого способа перезаписать значениеобъекта 3 – как говорилось в главе 4, целые числа относятся к категории неизменяемых, и потому эти объекты невозможно изменить.Переменные в языке Python, в отличие от других языков программирования,всегда являются указателями на объекты, а не метками областей памяти, доступных для изменения: запись нового значения в переменную не приводитк изменению первоначального объекта, но приводит к тому, что переменнаяначинает ссылаться на совершенно другой объект. В результате инструкцияприсваивания может воздействовать только на одну переменную.
Однако когда в уравнении появляются изменяемые объекты и операции, их изменяющие,картина несколько меняется. Чтобы узнать как, давайте двинемся дальше.Разделяемые ссылки и изменяемые объектыКак будет показано дальше в этой части книги, существуют такие объектыи операции, которые приводят к изменению самих объектов. Например, операция присваивания значения элементу списка фактически изменяет сам список вместо того, чтобы создавать совершенно новый объект списка. При работе с объектами, допускающими такие изменения, необходимо быть особенновнимательными при использовании разделяемых ссылок, так как изменениеодного имени может отразиться на других именах.Чтобы проиллюстрировать сказанное, возьмем в качестве примера объектысписков, о которых рассказывалось в главе 4.
Напомню, что списки, которыеподдерживают возможность присваивания значений элементам, – это простоколлекции объектов, которые в программном коде записываются как литералы в квадратных скобках:>>> L1 = [2, 3, 4]>>> L2 = L1В данном случае L1 – это список, содержащий объекты 2, 3 и 4. Доступ к элементам списка осуществляется по их индексам; так, L1[0] ссылается на объект2, то есть на первый элемент в списке L1. Безусловно, списки являются полноценными объектами, такими же, как целые числа и строки. После выполнениядвух приведенных выше инструкций L1 и L2 будут ссылаться на один и тот жеобъект, так же, как переменные a и b в предыдущем примере (рис. 6.2).
Точнотак же, если теперь добавить еще одну инструкцию:>>> L1 = 24переменная L1 будет ссылаться на другой объект, а L2 по-прежнему будет ссылаться на первоначальный список. Однако если синтаксис последней инструкции чуть-чуть изменить, эффект получится радикально другим:202Глава 6. Интерлюдия о динамической типизации>>> L1 = [2, 3, 4]>>> L2 = L1>>> L1[0] = 24# Изменяемый объект# Создание второй ссылки на тот же самый объект# Изменение объекта>>> L1[24, 3, 4]>>> L2[24, 3, 4]# Переменная L1 изменилась# Но так же изменилась и переменная L2!Здесь мы не изменяем сам объект L1, изменяется компонент объекта, на который ссылается L1. Данное изменение затронуло часть самого объекта списка.Поскольку объект списка разделяется разными переменными (ссылки на негонаходятся в разных переменных), то изменения в самом списке затрагиваютне только L1, то есть следует понимать, что такие изменения могут сказываться в других частях программы.
В этом примере изменения обнаруживаютсятакже в переменной L2, потому что она ссылается на тот же самый объект, чтои L1. Здесь мы фактически не изменяли L2, но значение этой переменной изменилось.Как правило, это именно то, что требовалось, но вы должны понимать, как этопроисходит. Это – поведение по умолчанию: если вас оно не устраивает, можно потребовать от интерпретатора, чтобы вместо создания ссылок он выполнялкопирование объектов. Скопировать список можно несколькими способами,включая встроенную функцию list и модуль copy из стандартной библиотеки.Однако самым стандартным способом копирования является получение срезаот начала и до конца списка (подробнее об этой операции рассказывается в главах 4 и 7):>>> L1 = [2, 3, 4]>>> L2 = L1[:]# Создается копия списка L1>>> L1[0] = 24>>> L1[24, 3, 4]>>> L2[2, 3, 4]# L2 не измениласьЗдесь изменения в L1 никак не отражаются на L2, потому что L2 ссылается накопию объекта, на который ссылается переменная L1.
То есть эти переменныеуказывают на различные области памяти.Обратите внимание, что способ, основанный на получении среза, неприменимв случае с другим изменяемым базовым типом – со словарями, потому что словари не являются последовательностями. Чтобы скопировать словарь, необходимо воспользоваться методом X.copy(). Следует также отметить, что модульcopy из стандартной библиотеки имеет в своем составе универсальную функцию, позволяющую копировать объекты любых типов, включая вложенныеструктуры (например, словари с вложенными списками):import copyX = copy.copy(Y)# Создание “поверхностной” копии любого объекта YX = copy.deepcopy(Y) # Создание полной копии: копируются все вложенные частиВ главах 8 и 9 мы будем рассматривать списки и словари во всей полноте и тамже вернемся к концепции разделяемых ссылок и копирования.
А пока простодержите в уме, что объекты, допускающие изменения в них самих (то есть из-203Разделяемые ссылкименяемые объекты), всегда подвержены описанным эффектам. В число такихобъектов в языке ���������������������������������������������������������Python���������������������������������������������������попадают списки, словари и некоторые объекты, объявленные с помощью инструкции class. Если такое поведение является нежелательным, вы можете просто копировать объекты.Разделяемые ссылки и равенствоВ интересах полноты обсуждения должен заметить, что возможность сборкимусора, описанная ранее в этой главе, может оказаться более принципиальным понятием, чем литералы для объектов некоторых типов. Рассмотрим следующие инструкции:>>> x = 42>>> x = ‘shrubbery’ # Объект 42 теперь уничтожен?Так как интерпретатор Python кэширует и повторно использует малые целыечисла и небольшие строки, о чем уже упоминалось ранее, объект 42 скорее всего не будет уничтожен.
Он, вероятнее всего, останется в системной таблице дляповторного использования, когда вы вновь сгенерируете число 42 в программном коде. Однако большинство объектов уничтожаются немедленно, как только будет потеряна последняя ссылка, особенно те, к которым применение механизма кэширования не имеет смысла.Например, согласно модели ссылок в языке Python, существует два разныхспособа выполнить проверку равенства. Давайте создадим разделяемую ссылку для демонстрации:>>> L>>> M>>> LTrue>>> LTrue= [1, 2, 3]= L# M и L – ссылки на один и тот же объект== M# Одно и то же значениеis M# Один и тот же объектПервый способ, основанный на использовании оператора ==, проверяет, равныли значения объектов. В языке ��������������������������������������������Python практически�������������������������������������всегда используется именно этот способ.
Второй способ, основанный на использовании оператора is, проверяет идентичность объектов. Он возвращает значение True, только если обаимени ссылаются на один и тот же объект, вследствие этого он является болеестрогой формой проверки равенства.На самом деле оператор is просто сравнивает указатели, которые реализуютссылки, и тем самым может использоваться для выявления разделяемых ссылок в программном коде.
Он возвращает значение False, даже если имена ссылаются на эквивалентные, но разные объекты, как, например, в следующемслучае, когда выполняются два различных литеральных выражения:>>> L>>> M>>> LTrue>>> LFalse= [1, 2, 3]= [1, 2, 3] # M и L ссылаются на разные объекты== M# Одно и то же значениеis M# Но разные объектыПосмотрите, что происходит, если те же самые действия выполняются над малыми целыми числами:204Глава 6. Интерлюдия о динамической типизации>>> X>>> Y>>> XTrue>>> XTrue= 42= 42 # Должно получиться два разных объекта== Yis Y # Тот же самый объект: кэширование в действии!В этом примере переменные X и Y должны быть равны (==, одно и то же значение), но не эквивалентны (is, один и тот же объект), потому что было выполнено два разных литеральных выражения.
Однако из-за того, что малые целыечисла и строки кэшируются и используются повторно, оператор is сообщает,что переменные ссылаются на один и тот же объект.Фактически если вы действительно хотите взглянуть на работу внутреннихмеханизмов, вы всегда можете запросить у интерпретатора количество ссылокна объект: функция getrefcount из стандартного модуля sys возвращает значение поля счетчика ссылок в объекте.
Когда я, например, запросил количествоссылок на целочисленный объект 1 в среде разработки IDLE, я получил число837 (большая часть ссылок была создана системным программным кодом самой IDLE, а не мною):>>> import sys>>> sys.getrefcount(1)837# 837 указателей на этот участок памятиТакое кэширование объектов и повторное их использование не будет иметьбольшого значения для вашего программного кода (если вы не используете оператор is!). Так как числа и строки не могут изменяться, совершенно неважно,сколько ссылок указывает на один и тот же объект. Однако такое поведениенаглядно демонстрирует один из реализуемых Python способов оптимизации,направленной на повышение скорости выполнения.Динамическая типизация повсюдуВ действительности вам совсем не нужно рисовать схемы с именами, объектами, кружочками и стрелками, чтобы использовать Python.
Однако в самомначале пути такие схемы иногда помогают разобраться в необычных случаях.Если после передачи изменяемого объекта в другую часть программы он возвращается измененным, значит, вы стали свидетелем того, о чем рассказывалось в этой главе.Более того, если к настоящему моменту динамическая типизация кажетсявам немного непонятной, вы, вероятно, захотите устранить это недопониманиев будущем. Поскольку в языке Python все основано на присваивании и на ссылках, понимание основ этой модели пригодится во многих ситуациях.
Как будетпоказано позже, одна и та же модель используется в операторах присваивания,при передаче аргументов функциям, в переменных цикла for, при импортировании модулей и так далее. Но к счастью, в Python реализована всего однамодель присваивания! Как только вы разберетесь в динамической типизации,вы обнаружите, что подобные принципы применяются повсюду в этом языкепрограммирования.На практическом уровне наличие динамической типизации означает, что вампридется писать меньше программного кода.