Главная страница

Чистыйкод дляпродолжающи х


Скачать 7.85 Mb.
НазваниеЧистыйкод дляпродолжающи х
Дата13.05.2023
Размер7.85 Mb.
Формат файлаpdf
Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
ТипДокументы
#1127485
страница14 из 40
1   ...   10   11   12   13   14   15   16   17   ...   40
Объекты, значения, экземпляры и идентичность
Объект (object) представляет некоторый фрагмент данных: число, текст или более сложную структуру данных (такую как список или словарь). Все объекты могут сохраняться в переменных, передаваться в аргументах при вызове функций и воз- вращаться из вызовов функций.
Каждый объект характеризуется значением, идентичностью и типом данных (value, identity и data type). Значение — данные, представляемые объектом (например, целое число 42 или строка 'hello'
). И хотя это создает неоднозначность, некоторые програм- мисты используют термин «значение» как синоним объекта, особенно для простых типов данных (например, целых чисел или строк). Так, переменная, которая содержит данные
42
, является переменной, которая содержит целое значение, но также можно сказать, что это переменная, содержащая целочисленный объект со значением
42
Объект создается с идентичностью — уникальным целым числом, которое можно просмотреть вызовом функции id()
. Например, введите следующий код в интерак- тивной оболочке:
>>> spam = ['cat', 'dog', 'moose']
>>> id(spam)
33805656

140
Глава 7.Жаргон программистов
МЕТАФОРЫ ПЕРЕМЕННЫХ: КОРОБКИ И НАКЛЕЙКИ
Во многих учебниках начального уровня переменные сравниваются с короб- ками, что представляет собой чрезмерное упрощение. Переменную удобно представить как коробку, в которой находится значение (рис. 7.1), но когда речь заходит о ссылках, метафора начинает рассыпаться. В только что рас- смотренном примере в переменных spam и eggs не хранились разные словари; в них хранились ссылки на один словарь, находящийся в памяти компьютера.
Рис. 7.1. Во многих книгах переменные сравниваются с коробками, в которых хранятся значения
В Python все переменные с технической точки зрения являются ссылками, а не контейнерами значений, независимо от их типа данных. Метафора ко- робки проста, но не идеальна. Вместо того чтобы рассматривать переменные как коробки, вы также можете рассматривать переменные как наклейки для объектов в памяти. На рис. 7.2 изображены наклейки для переменных spam и eggs из предыдущего примера.
Рис. 7.2. Переменные также можно представить как наклейки для значений

Определения
141
Так как несколько переменных могут ссылаться на один объект, этот объ- ект будет «храниться» в нескольких переменных. Один объект нельзя положить сразу в несколько коробок, поэтому будет проще использовать метафору наклейки. Дополнительная информация по этой теме содержит- ся в докладе Неда Бэтчелдера (Ned Batchelder) на конференции PyCon
2015, «Facts and Myths about Python Names and Values» (https://youtu.
be/_AEJHKGk9ns).
Переменная spam содержит объект с типом данных списка и значением
['cat',
'dog',
'moose']
. Ее идентичность равна
33805656
, хотя числовое идентифициру- ющее значение вычисляется заново при запуске программы, так что с большой вероятностью вы будете получать разные числа на вашем компьютере.
После того как объект будет создан, его идентичность не изменяется на про- тяжении выполнения программы. Хотя тип данных и идентичность объекта не изменяются во время выполнения, значение объекта может измениться, как мы видим в следующем примере:
>>> spam.append('snake')
>>> spam
['cat', 'dog', 'moose', 'snake']
>>> id(spam)
33805656
Теперь список также содержит элемент 'snake'
. Но как видно по вызову id(spam)
, его идентичность не изменилась — список остался тем же. Но давайте посмотрим, что произойдет, если ввести следующий код:
>>> spam = [1, 2, 3]
>>> id(spam)
33838544
Значение в spam было перезаписано новым объектом списка с новой идентичностью:
33838544
вместо
33805656
. Идентификатор (identifier) (такой как spam
) не следует путать с идентичностью (identity), потому что несколько идентификаторов могут ссылаться на объект. Так, в следующем примере двум переменным присваивается один и тот же словарь:
>>> spam = {'name': 'Zophie'}
>>> id(spam)
33861824
>>> eggs = spam
>>> id(eggs)
33861824

142
Глава 7.Жаргон программистов
Идентичности обоих идентификаторов, spam и eggs
, равны
33861824
, потому что они относятся к одному объекту словаря. Теперь измените значение spam в инте- рактивной оболочке:
>>> spam = {'name': 'Zophie'}
>>> eggs = spam
>>> spam['name'] = 'Al'

>>> spam
{'name': 'Al'}
>>> eggs
{'name': 'Al'}

Как видите, изменения spam

, как по волшебству, также появляются в eggs

Это объясняется тем, что оба идентификатора относятся к одному и тому же объекту.
Если не понимать, что оператор присваивания
=
всегда копирует ссылку, а не объ- ект, можно внести в программу ошибку: вы думаете, что копируете объект, тогда как в действительности копируется ссылка на исходный объект. К счастью, это не создает проблем с неизменяемыми значениями (целыми числами, строками и кортежами) по причинам, которые объясняются в подразделе «Изменяемость и неизменяемость» на с. 143.
Оператор is может использоваться для проверки тождественности, то есть того, что два объекта имеют одинаковую идентичность. С другой стороны, оператор
==
проверяет только равенство значений двух объектов. Можно считать, что x
is y
является сокращенной записью для id(x)
==
id(y)
. Введите следующий фрагмент в интерактивной оболочке, чтобы понять суть различий:
>>> spam = {'name': 'Zophie'}
>>> eggs = spam

>>> spam is eggs
True
>>> spam == eggs
True
>>> bacon = {'name': 'Zophie'}

>>> spam == bacon
True
>>> spam is bacon
False
Переменные spam и eggs ссылаются на один объект словаря

, так что их идентич- ности и значения одинаковы. Но переменная bacon ссылается на другой объект словаря

, хотя он и содержит данные, совпадающие с данными spam и eggs
. Со- впадение данных означает, что bacon содержит то же значение, что и spam и eggs
, но это два разных объекта с двумя разными идентичностями.

Определения
143
Элементы
В Python объект, находящийся в объекте-контейнере (таком как список или сло- варь), также называется элементом (item, element). Например, строки в списке
['dog',
'cat',
'moose']
являются объектами, но они также называются элементами.
Изменяемость и неизменяемость
Как я упоминал ранее, все объекты в Python характеризуются значением, типом данных и идентичностью и из всех этих атрибутов изменяться может только значе- ние. Если значение объекта можно изменить, этот объект называется изменяемым
(mutable). Если значение объекта изменить нельзя, объект называется неизменяе-
мым (immutable). В табл. 7.2 перечислены некоторые изменяемые и неизменяемые типы данных в Python.
Таблица 7.2. Примеры изменяемых и неизменяемых типов данных Python
Изменяемые типы данных
Неизменяемые типы данных
List (Список)
Integer (Целое число)
Dictionaries (Словари)
Floating-point number (Число с плавающей точкой)
Sets (Множества)
Boolean (Логический)
Bytearray (Массив байтов)
String (Строка)
Array (Массив)
Frozen set (Зафиксированное множество)
Bytes (Байты)
Tuple (Кортеж)
Когда вы перезаписываете переменную, может показаться, что вы изменяете зна- чение объекта, как в следующем примере:
>>> spam = 'hello'
>>> spam
'hello'
>>> spam = 'goodbye'
>>> spam
'goodbye'
Но в этом коде вы не заменяете значение объекта 'hello'
значением 'goodbye'
Это два разных объекта, а вы только переключаете переменную spam так, чтобы

144
Глава 7.Жаргон программистов она содержала ссылку не на объект 'hello'
, а на объект 'goodbye'
. Чтобы убе- диться в этом, воспользуемся функцией id()
для вывода идентичностей двух объектов:
>>> spam = 'hello'
>>> id(spam)
40718944
>>> spam = 'goodbye'
>>> id(spam)
40719224
Эти два объекта обладают разными идентичностями (
40718944
и
40719224
), потому что это два разных объекта. Но у переменных, ссылающихся на изменяемые объек- ты, значения могут изменяться на месте (in-place). Например, введите следующий фрагмент в интерактивной оболочке:
>>> spam = ['cat', 'dog']
>>> id(spam)
33805576
>>> spam.append('moose')

>>> spam[0] = 'snake'

>>> spam
['snake', 'dog', 'moose']
>>> id(spam)
33805576
Метод append()

и присваивание элементу по индексу

изменяют значение списка на месте. И хотя значение списка изменилось, его идентичность осталась прежней (
33805576
). Но при выполнении конкатенации списка с использованием оператора
+
вы создаете новый объект (с новой идентичностью), который заменяет старый список:
>>> spam = spam + ['rat']
>>> spam
['snake', 'dog', 'moose', 'rat']
>>> id(spam)
33840064
Конкатенация списков создает новый список с новой идентичностью. Когда это происходит, старый список будет со временем удален из памяти сборщиком мусора. За информацией о том, какие методы и операции изменяют объекты на месте, а какие их перезаписывают, обращайтесь к документации Python. Можно запомнить хорошее практическое правило: если вы видите литерал в исходном коде (например,
['rat']
в приведенном примере), то, скорее всего, Python создаст новый объект. Метод, который вызывается для объекта (например, append()
), часто изменяет объект на месте.

Определения
145
Присваивание проще выполняется для объектов с неизменяемыми типами данных
(целые числа, строки, кортежи и т. д.). Например, введите следующий фрагмент в интерактивной оболочке:
>>> bacon = 'Goodbye'
>>> id(bacon)
33827584
>>> bacon = 'Hello'

>>> id(bacon)
33863820
>>> bacon = bacon + ', world!'

>>> bacon
'Hello, world!'
>>> id(bacon)
33870056 3 >>> bacon[0] = 'J'
Traceback (most recent call last):
File "", line 1, in
TypeError: 'str' object does not support item assignment
Строки являются неизменяемыми, так что вы не сможете изменить их значение. Хотя все выглядит так, будто значение строки в переменной bacon меняется с 'Goodbye'
на 'Hello'

, в действительности она замещается новым объектом строки с новой иден- тичностью. Аналогичным образом выражение, использующее конкатенацию строк, создает новый строковый объект

с новой идентичностью. Попытки модифициро- вать строку «на месте» посредством присваивания элементу запрещены в Python 3.
Значение кортежа определяется как совокупность содержащихся в нем объектов и порядка этих объектов. Кортежи представляют собой неизменяемые последо- вательности объектов, в которых значения заключаются в круглые скобки. Это означает, что перезапись элементов кортежа невозможна:
>>> eggs = ('cat', 'dog', [2, 4, 6])
>>> id(eggs)
39560896
>>> id(eggs[2])
40654152
>>> eggs[2] = eggs[2] + [8, 10]
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
Однако изменяемый список в неизменяемом кортеже все равно можно изменить на месте:
>>> eggs[2].append(8)
>>> eggs[2].append(10)
>>> eggs
('cat', 'dog', [2, 4, 6, 8, 10])

146
Глава 7.Жаргон программистов
>>> id(eggs)
39560896
>>> id(eggs[2])
40654152
И хотя это экзотический случай, о нем важно помнить. Кортеж по-прежнему со- держит ссылки на те же объекты (рис. 7.3). Но если кортеж содержит изменяемый объект и этот объект изменяет свое значение, то значение кортежа также изменится.
Я, как и почти все программисты, пишущие на Python, называю кортежи неизме- няемыми. Но вопрос о том, можно ли назвать некоторые кортежи изменяемыми, зависит от определения. Эта тема более подробно рассматривается в моем докладе на конференции PyCascades 2019 «The Amazing Mutable, Immutable Tuple» (https://
invpy.com/amazingtuple/). Или же загляните в главу 2 книги «Fluent Python»
(O’Reilly Media, 2015) Лучано Рамальо (Luciano Ramalho).
Список изменяется
Кортеж не изменяется
Рис. 7.3. Хотя набор объектов в кортеже неизменяем, сами объекты могут быть изменяемыми
Индексы, ключи и хеш-коды
Списки и словари Python являются значениями, которые могут содержать на- боры других значений. Для обращения к этим значениям используется оператор
индексирования, который состоит из пары квадратных скобок (
[]
) и целого числа, называемого индексом; оно указывает, к какому значению вы хотите обратиться.
Чтобы понять, как работает индексирование списков, введите следующий фрагмент в интерактивной оболочке:
>>> spam = ['cat', 'dog', 'moose']
>>> spam[0]
'cat'
>>> spam[-2]
'dog'

Определения
147
В этом примере число
0
— индекс. Первый индекс равен 0, а не 1, потому что в Python
(как и в большинстве языков) нумерация индексов начинается с 0. Языки, в которых нумерация индексов начинается с 1, встречаются нечасто: самые заметные приме- ры — Lua и R. Python также поддерживает отрицательные индексы;
–1
обозначает последний элемент списка,
–2
— предпоследний элемент и т. д. Отрицательный индекс spam[–n]
можно рассматривать как эквивалент spam[len(spam)
- n]
ПРИМЕЧАНИЕ
Компьютерный теоретик, певец и автор песен Стэн Келли-Бутл (Stan Kelly-Bootle) однаж- ды пошутил: «Должны ли индексы массивов начинаться с 0 или 1? Мое компромиссное предложение 0,5 было отвергнуто без должного рассмотрения».
Оператор индексирования также может использоваться со списковым литералом, хотя в реальном коде все эти квадратные скобки кажутся непонятными и избы- точными:
>>> ['cat', 'dog', 'moose'][2]
'moose'
Индексирование также применяют не только со списками, но и с другими значе- ниями — например, со строками для получения отдельных символов:
>>> 'Hello, world'[0]
'H'
В словарях Python информация структурирована в виде пар «ключ — значение»:
>>> spam = {'name': 'Zophie'}
>>> spam['name']
'Zophie'
И хотя индексы списков ограничиваются целыми числами, оператор индексирования у словарей Python использует ключ, которым может быть любой хешируемый объ- ект. Хеш-код представляет собой целое число, своего рода отличительный признак некоторого значения. Хеш-код объекта никогда не изменяется на протяжении его жизненного цикла, и объекты с одинаковыми значениями должны иметь одинаковые хеш-коды. Строка 'name'
в этом экземпляре является ключом для значения 'Zophie'
Функция hash()
возвращает хеш-код объекта, если объект является хешируемым.
Неизменяемые объекты (строки, целые числа, числа с плавающей точкой, кортежи) могут быть хешируемыми. Списки (а также другие изменяемые объекты) хешируе- мыми не являются. Введите следующий фрагмент в интерактивной оболочке:
>>> hash('hello')
-1734230105925061914
>>> hash(42)

148
Глава 7.Жаргон программистов
42
>>> hash(3.14)
322818021289917443
>>> hash((1, 2, 3))
2528502973977326415
>>> hash([1, 2, 3])
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'
Хотя подробности выходят за рамки книги, хеш-код ключа используется для поиска элементов в структурах данных (словарях и множествах). Это объясняет, почему изменяемый список не может использоваться в ключах словарей:
>>> d = {}
>>> d[[1, 2, 3]] = 'some value'
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'
Хеш-код отличается от идентичности. Два разных объекта с одинаковыми значе- ниями имеют разные идентичности, но одинаковые хеш-коды. Например, введите следующий фрагмент в интерактивной оболочке:
>>> a = ('cat', 'dog', 'moose')
>>> b = ('cat', 'dog', 'moose')
>>> id(a), id(b)
(37111992, 37112136)
>>> id(a) == id(b)

False
>>> hash(a), hash(b)
(-3478972040190420094, -3478972040190420094)
>>> hash(a) == hash(b)

True
Кортежи, на которые ссылаются a
и b
, имеют разные идентичности

, но так как они имеют одинаковые значения, из этого следует, что их хеш-коды идентичны

Обратите внимание: кортеж является хешируемым, если он содержит только хе- шируемые элементы. Так как ключами словарей могут быть только хешируемые элементы, кортеж, содержащий нехешируемый список, не может использоваться в качестве ключа. Введите следующий фрагмент в интерактивной оболочке:
>>> tuple1 = ('cat', 'dog')
>>> tuple2 = ('cat', ['apple', 'orange'])
>>> spam = {}
>>> spam[tuple1] = 'a value'

>>> spam[tuple2] = 'another value'

Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'

Определения
149
Обратите внимание: кортеж tuple1
является хешируемым

, но tuple2
содержит нехешируемый список

, а следовательно, хешируемым не является.
Контейнеры, последовательности, отображения и разновидности множеств
Смысл терминов «контейнер», «последовательность» и «отображение» в Python не всегда применим к другим языкам программирования. В Python контейнером называется объект любого типа данных, который может содержать несколько других объектов. Из всех видов контейнеров в Python чаще всего используются списки и словари.
Последовательность (sequence) представляет собой объект любого контейнерного типа данных с упорядоченными значениями, к которым можно обращаться по целым индексам. Строки, кортежи, списки и байтовые объекты относятся к типам данных последовательностей. Объекты этих типов могут обращаться к значениям по целым индексам в операторе индексирования (квадратные скобки
[
и
]
), а так- же могут передаваться функции len()
. Под упорядоченностью мы понимаем, что в последовательности есть первое значение, второе значение и т. д. Например, сле- дующие два списковых значения равными не считаются, потому что их элементы следуют в разном порядке:
>>>
1   ...   10   11   12   13   14   15   16   17   ...   40


написать администратору сайта