Математический анализ. 3е издание
Скачать 4.86 Mb.
|
a = 3 >>> b = a В результате выполнения этих двух инструкций получается схема взаимоотношений, отраженная на рис. 6.2. Вторая инструкция вынуж дает интерпретатор создать переменную b и использовать для инициа лизации переменную a, при этом она замещается объектом, на который Имена Ссылки Объекты a 3 b a=3 b=a Рис. 6.2. Имена и объекты после выполнения инструкции присваивания b = a. Переменная b превращается в ссылку на объект 3. С технической точки зрения переменная в действительности является указателем на область памяти объекта, созданного в результате выполнения литерального выражения 3 172 Глава 6. Интерлюдия о динамической типизации ссылается (3), и b превращается в ссылку на этот объект. В результате переменные a и b ссылаются на один и тот же объект (то есть указывают на одну и ту же область в памяти). В языке Python это называется разде+ ляемая ссылка – несколько имен ссылаются на один и тот же объект. Далее добавим еще одну инструкцию: >>> a = 3 >>> b = a >>> a = 'spam' Как во всех случаях присваивания в языке Python, в результате вы полнения этой инструкции создается новый объект, представляющий строку 'spam', а ссылка на него записывается в переменную a. Однако эти действия не оказывают влияния на переменную b – она попрежне му ссылается на первый объект, целое число 3. В результате схема ссы лок приобретает вид, показанный на рис. 6.3. То же самое произошло бы, если бы ссылка на объект 'spam' вместо пере менной a была присвоена переменной b – изменилась бы только перемен ная b, но не a. Аналогичная ситуация возникает, даже если тип объек та не изменяется. Например, рассмотрим следующие три инструкции: >>> a = 3 >>> b = a >>> a = a + 2 В этой последовательности происходят те же самые события: интер претатор Python создает переменную a и записывает в нее ссылку на объект 3. После этого он создает переменную b и записывает в нее ту же ссылку, что хранится в переменной a, как показано на рис. 6.2. Нако нец, последняя инструкция создает совершенно новый объект (в данном a 3 b Имена Ссылки Объекты ‘spam’ a=3 b=a a=‘spam’ Рис. 6.3. Имена и объекты после выполнения инструкции присваивания a = 'spam'. Переменная a ссылается на новый объект (то есть на область памяти), созданный в результате выполнения литерального выражения 'spam', но переменная b по+прежнему ссылается на первый объект 3. Так как эта операция присваивания никак не изменяет объект 3, она изменяет только переменную a, но не b Разделяемые ссылки 173 случае – целое число 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 попрежнему бу дет ссылаться на первоначальный список. Однако если синтаксис по следней инструкции чутьчуть изменить, эффект получится радикаль но другим: 174 Глава 6. Интерлюдия о динамической типизации >>> L1 = [2, 3, 4] # Изменяемый объект >>> L2 = L1 # Создание второй ссылки на тот же самый объект >>> L1[0] = 24 # Изменение объекта >>> L1 # Переменная L1 изменилась [24, 3, 4] >>> L2 # Но также изменилась и переменная L2! [24, 3, 4] Здесь мы не изменяем сам объект 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 # L2 не изменилась [2, 3, 4] Здесь изменения в L1 никак не отражаются на L2, потому что L2 ссыла ется на копию объекта, на который ссылается переменная L1. То есть эти переменные указывают на различные области памяти. Обратите внимание: способ, основанный на получении среза, неприме ним в случае с другим изменяемым базовым типом – со словарями, по тому что словари не являются последовательностями. Чтобы скопиро вать словарь, необходимо воспользоваться методом D.copy(). Следует также отметить, что модуль copy из стандартной библиотеки имеет в своем составе универсальную функцию, позволяющую копировать объекты любых типов, включая вложенные структуры (например, словари с вложенными списками): import copy X = copy.copy(Y) # Создание "поверхностной" копии любого объекта Y X = copy.deepcopy(Y) # Создание полной копии: копируются все вложенные части Разделяемые ссылки 175 В главах 8 и 9 мы будем рассматривать списки и словари во всей пол ноте и там же вернемся к концепции разделяемых ссылок и копирова ния. А пока просто держите в уме, что объекты, допускающие измене ния в них самих (то есть изменяемые объекты), всегда подвержены описанным эффектам. В число таких объектов в языке Python попада ют списки, словари и некоторые объекты, объявленные с помощью ин струкции class. Если такое поведение является нежелательным, вы можете просто копировать объекты. Разделяемые ссылки и равенство В интересах полноты обсуждения должен заметить, что возможность сборки мусора, описанная ранее в этой главе, может оказаться более принципиальным понятием, чем литералы для объектов некоторых типов. Рассмотрим следующие инструкции: >>> x = 42 >>> x = 'shrubbery' # Объект 42 теперь уничтожен? Так как интерпретатор Python кэширует и повторно использует малые целые числа и небольшие строки, о чем уже упоминалось ранее, объ ект 42 скорее всего не будет уничтожен. Он, скорее всего, останется в системной таблице для повторного использования, когда вы вновь сгенерируете число 42 в программном коде. Однако большинство объ ектов уничтожаются немедленно, как только будет потеряна послед няя ссылка, особенно те, к которым применение механизма кэширова ния не имеет смысла. Например, согласно модели ссылок в языке Python, существует два разных способа выполнить проверку равенства. Давайте создадим раз деляемую ссылку для демонстрации: >>> L = [1, 2, 3] >>> M = L # M и L – ссылки на один и тот же объект >>> L == M # Одно и то же значение True >>> L is M # Один и тот же объект True Первый способ, основанный на использовании оператора ==, проверяет, равны ли значения объектов. В языке практически всегда используется именно этот способ. Второй способ, основанный на использовании опе ратора is, проверяет идентичность объектов. Он возвращает значение True , только если оба имени ссылаются на один и тот же объект, вслед ствие этого он является более строгой формой проверки равенства. На самом деле оператор is просто сравнивает указатели, которые реа лизуют ссылки, и тем самым может использоваться для выявления разделяемых ссылок в программном коде. Он возвращает значение False , даже если имена ссылаются на эквивалентные, но разные объек ты, как, например, в следующем случае, когда выполняются два раз личных литеральных выражения: 176 Глава 6. Интерлюдия о динамической типизации >>> L = [1, 2, 3] >>> M = [1, 2, 3] # M и L ссылаются на разные объекты >>> L == M # Одно и то же значение True >>> L is M # Но разные объекты False Посмотрите, что происходит, если те же самые действия выполняются над малыми целыми числами: >>> X = 42 >>> Y = 42 # Должно получиться два разных объекта >>> X == Y True >>> X is Y # Тот же самый объект: кэширование в действии! True В этом примере переменные X и Y должны быть равны (==, одно и то же значение), но не эквивалентны (is, один и тот же объект), потому что было выполнено два разных литеральных выражения. Однако изза то го что малые целые числа и строки кэшируются и используются по вторно, оператор is сообщает, что переменные ссылаются на один и тот же объект. Фактически, если вы действительно хотите взглянуть на работу внут ренних механизмов, вы всегда можете запросить у интерпретатора ко личество ссылок на объект: функция getrefcount из стандартного модуля sys возвращает значение поля счетчика ссылок в объекте. Когда я, на пример, запросил количество ссылок на целочисленный объект 1 в сре де разработки IDLE, я получил число 837 (большая часть ссылок была создана системным программным кодом самой IDLE, а не мною): >>> import sys >>> sys.getrefcount(1) # 837 указателей на этот участок памяти 837 Такое кэширование объектов и повторное их использование будет ма лоприменимо к вашему программному коду (если вы не используете оператор is!). Так как числа и строки не могут изменяться, совершен но неважно, сколько ссылок указывает на один и тот же объект. Одна ко такое поведение наглядно демонстрирует один из реализуемых Py thon способов оптимизации, направленной на повышение скорости выполнения. Динамическая типизация повсюду В действительности вам совсем не нужно рисовать схемы с именами, объектами, кружочками и стрелками, чтобы использовать Python. Од нако в самом начале пути такие схемы иногда помогают разобраться в необычных случаях. Если после передачи изменяемого объекта в дру гую часть программы он возвращается измененным, значит, вы стали свидетелем того, о чем рассказывалось в этой главе. В заключение 177 Более того, если к настоящему моменту динамическая типизация ка жется вам немного непонятной, вы, вероятно, захотите устранить это недопонимание в будущем. Поскольку в языке Python все основано на присваивании и на ссылках, понимание основ этой модели пригодится во многих ситуациях. Как будет показано позже, одна и та же модель используется в операторах присваивания, при передаче аргументов функциям, в переменных цикла for, при импортировании модулей и т. д. Но к счастью, в Python реализована всего одна модель присваи вания! Как только вы разберетесь в динамической типизации, вы об наружите, что подобные принципы применяются повсюду в этом язы ке программирования. На практическом уровне наличие динамической типизации означает, что вам придется писать меньше программного кода. Однако не менее важно и то, что динамическая типизация составляет основу полимор физма – концепции, которая была введена в главе 4 и к которой мы еще вернемся далее в этой книге – в языке Python. Поскольку мы не ограни чены применением типов в программном коде на языке Python, он обла дает очень высокой гибкостью. Как будет показано далее, при правиль ном использовании динамической типизации и полиморфизма можно создавать такой программный код, который автоматически будет адап тироваться под новые требования в ходе развития вашей системы. В заключение В этой главе мы подробно рассмотрели модель динамической типиза ции в языке Python, то есть способ, который используется интерпрета тором для автоматического хранения информации о типах объектов и избавляет нас от необходимости вставлять код объявлений в наши сценарии. Попутно мы узнали, как реализована связь между перемен ными и объектами. Кроме того, мы исследовали понятие сборки мусо ра, узнали, как разделяемые ссылки на объекты могут оказывать воз действие на несколько переменных и как ссылки влияют на понятие равенства в языке Python. Поскольку в языке Python имеется всего одна модель присваивания, и она используется повсюду в этом языке, очень важно, чтобы вы разо брались в ней, прежде чем двигаться дальше. Контрольные вопросы к этой главе должны помочь вам повторить некоторые идеи этой гла вы. После этого в следующей главе мы возобновим наш обзор объек тов, приступив к изучению строк. Закрепление пройденного Контрольные вопросы 1. Взгляните на следующие три инструкции. Изменится ли значение переменной A? 178 Глава 6. Интерлюдия о динамической типизации A = "spam" B = A B = "shrubbery" 2. Взгляните на следующие три инструкции. Изменится ли значение переменной A? A = ["spam"] B = A B[0] = "shrubbery" 3. А что можно сказать об этом случае? Изменится ли значение пере менной A? A = ["spam"] B = A[:] B[0] = "shrubbery" Ответы 1. Нет, значением переменной A попрежнему будет строка 'spam'. Ко гда переменной B присваивается строка 'shrubbery', она просто на чинает указывать на другой строковый объект. Первоначально пе ременные A и B разделяют (то есть ссылаются или указывают) один и тот же строковый объект со значением 'spam', но в языке Python два имени никак не связаны между собой. Таким образом, назначе ние переменной B ссылки на другой объект не оказывает влияние на переменную A. То же самое было бы справедливо, если бы послед няя инструкция имела вид B = B + 'shrubbery', так как операция кон катенации создает в результате новый объект, ссылка на который затем записывается в переменную B. Мы не можем изменять сами строковые объекты (или числа, или кортежи), потому что они отно сятся к категории неизменяемых объектов. 2. Да, теперь значением переменной A будет ["shrubbery"]. Формально мы не изменили ни одну из переменных, ни A, ни B, но мы изменили часть самого объекта, на который они ссылаются (указывают), по средством переменной B. Поскольку A ссылается на тот же самый объект, что и B, изменения можно обнаружить и с помощью пере менной A. 3. Нет, значением переменной A попрежнему будет строка 'spam'. Из менение самого объекта с помощью ссылки B не оказывает влияние на переменную A по той причине, что выражение получения среза создает копию объекта списка, прежде чем присвоить ссылку пере менной B. После второго оператора присваивания в программе бу дет существовать два разных объекта списков с одинаковыми зна чениями (в языке Python мы говорим: «равны, но не эквивалент ны»). Третья инструкция изменяет значение объекта списка, на ко торый ссылается переменная B, а объект, на который ссылается переменная A, остается неизменным. 7 Строки Следующий основной тип на пути нашего исследования встроенных объектов языка Python – это строки, упорядоченные последователь ности символов, используемые для хранения и представления тексто вой информации. Мы коротко познакомились со строками в главе 4. Здесь мы займемся более глубоким их исследованием и восполним не которые подробности, которые ранее были опущены. С функциональной точки зрения строки могут использоваться для представления всего, что только может быть выражено в текстовой фор ме: символы и слова (например, ваше имя), содержимое текстовых фай лов, загруженных в память, адреса в Интернете, программы на языке Python и т. д. Возможно, вам уже приходилось использовать строки в других язы ках программирования. Строки в языке Python играют ту же роль, что и массивы символов в языке C, но они являются инструментом более высокого уровня, нежели простой массив символов. В отличие от C, строки в языке Python обладают мощным набором средств для их обра ботки. Кроме того, в отличие от таких языков, как C, в языке Python отсутствует специальный тип для представления единственного симво ла (как тип char в языке C), поэтому в случае необходимости использу ются односимвольные строки. Строго говоря, строки в языке Python относятся к категории неизме няемых последовательностей, в том смысле, что символы, которые они содержат, имеют определенный порядок следования слева напра во и что сами строки невозможно изменить. Фактически, строки – это первый представитель большого класса объектов, называемых последо вательностями, которые мы будем здесь изучать. Обратите особое вни мание на операции над последовательностями, представленные в этой главе, так как они похожим образом работают и с другими типами |