Книга: Как устроен Python. Гид для разработчиков, программистов и интересующихся
Назад: 6. Переменные
Дальше: 8. Числа

7. Подробнее об объектах

В этой главе объекты будут рассмотрены более подробно. Речь пойдет о трех важных свойствах объектов:

• идентификатор;

• тип;

• значение.

7.1. Идентификатор

На самом низком уровне идентификатор определяет местонахождение объекта в памяти компьютера. В Python существует встроенная функция id, которая возвращает идентификатор объекта:

>>> name = "Matt"

>>> id(name)

140310794682416

При выполнении этого кода идентификатор строки "Matt" выводится в форме 140310794682416 (это число определяет адрес оперативной памяти вашего компьютера). Обычно идентификатор изменяется в зависимости от компьютера и сеанса командного интерпретатора, однако на протяжении жизненного цикла программы идентификатор объекта остается неизменным.

Одну корову можно пометить двумя бирками; точно так же две переменные могут указывать на один объект. Если вы хотите, чтобы другая переменная (скажем, с именем first) тоже указывала на объект, на который указывает name, выполните следующую команду:

>>> first = name

Команда приказывает Python назначить переменной first такой же идентификатор, как у name. При выполнении функции id для двух переменных будут получены одинаковые значения:

>>> id(first)

140310794682416

>>> id(name)

140310794682416

513789.png 

Рис. 7.1. На диаграмме показано, что происходит при связывании переменной с существующей переменной. Обе переменные указывают на один объект. Обратите внимание: переменная при этом не копируется! Также обратите внимание на то, что объект обладает значением “Matt”, типом и идентификатором

Какая польза от идентификатора, спросите вы? Вообще-то пользы немного. При программировании на Python вас обычно не интересуют такие низкоуровневые подробности, как адрес объекта в памяти. Однако при помощи идентификатора можно показать время создания объектов и возможно ли их изменение. Кроме того, идентификаторы косвенно используются для проверки тождественности оператором is.

Оператор is проверяет тождественность, то есть он проверяет, указывают ли две переменные на один и тот же объект:

>>> first is name

True

Если вывести в REPL first и name, будут выведены одинаковые значения, потому что оба имени указывают на один объект:

>>> print(first)

Matt

>>> print(name)

Matt

Бирку можно снять с одной коровы и повесить на другую. Точно так же и переменную можно перевести с одного объекта на другой. Например, мы можем заставить переменную name указывать на новый объект. Вы увидите, что идентификатор name изменился, тогда как идентификатор first остался прежним:

>>> name = 'Fred'

>>> id(name)

140310794682434

>>> id(first)

140310794682416

7.2. Тип

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

>>> type(name)

<class 'str'>

В данном случае функция type сообщает, что переменная name указывает на строку (str).

В следующей таблице перечислены типы различных объектов Python.

Объект

Тип

Строка

str

Целое число

int

Число с плавающей точкой

float

Список

list

Словарь

dict

Кортеж

tuple

Функция

function

Класс, определяемый пользователем

type

Экземпляр класса

class

Встроенная функция

builtin_function _or_method

type

type

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

Однако иногда в программе имеются данные, которые нужно преобразовать к другому типу. Такая ситуация типична для чтения данных из стандартного ввода: данные обычно поступают в виде строки, и эту строку нужно преобразовать в число. Python предоставляет встроенные классы str, int, float, list, dict и tuple, которые при необходимости выполняют преобразование к соответствующему типу:

>>> str(0)

'0'

 

>>> tuple([1,2])

(1, 2)

>>> list('abc')

['a', 'b', 'c']

ПРИМЕЧАНИЕ

Термин «утиная типизация» происходит от поговорки XVIII века, в которой говорилось о механической утке: «Если оно выглядит как утка, ходит как утка и крякает как утка, то это утка».

Впрочем, лично я предпочитаю сцену из фильма «Монти Пайтон и Священный Грааль», где в одной женщине распознают ведьму, потому что она весит столько же, сколько весит утка. (Если вам это кажется странным, посмотрите фильм — мне он нравится.) Идея в том, что женщина обладает такими же характеристиками (вес), как и утка, поэтому ее следует считать ведьмой.

Python действует аналогично. Если вы хотите использовать объект для перебора элементов, вы помещаете его в цикл for. Вы не проверяете, является ли он списком или субклассом списка, а просто выполняете перебор. Если вы хотите выполнить операцию сложения, вы не проверяете, является ли объект числом или строкой (или другим типом, поддерживающим сложение). Если операция завершится неудачей — нормально, это всего лишь говорит о том, что вы предоставили неправильный тип.

Если вы знакомы с объектно-ориентированным программированием, «утиная типизация» ослабляет жесткие требования субклассирования. Вместо того чтобы наследовать от нескольких классов, чтобы воспользоваться предоставляемым ими поведением, вы должны реализовать протоколы (обычно для этого нужно определить один-два метода). Например, чтобы создать класс, который может использоваться для сложения, необходимо реализовать метод .__add__. Любой класс может определить этот метод и реагировать на операцию сложения.

7.3. Изменяемость

Другое интересное свойство объекта — его изменяемость. Многие объекты являются изменяемыми, другие неизменяемы. Изменяемые объекты допускают изменение своего значения «на месте» — иначе говоря, их состояние можно изменить, но их идентификатор останется неизменным. Неизменяемые объекты не позволяют изменить свое значение. Вместо этого их переменная связывается с новым объектом, но это также приводит к изменению идентификатора переменной.

В языке Python словари и списки являются изменяемыми типами. Строки, кортежи, целые числа и числа с плавающей точкой относятся к неизменяемым типам. Следующий пример демонстрирует, что идентификатор переменной, содержащей целое число, изменится при изменении значения. Сначала программа присваивает целое число переменной age и проверяет идентификатор:

>>> age = 1000

>>> id(age)

140310794682416

Обратите внимание: если теперь изменить значение целого числа, то у него появится новый идентификатор:

>>> age = age + 1

>>> id(age)

140310793921824

А теперь рассмотрим пример изменения списка. Начнем с пустого списка и проверим его идентификатор. Обратите внимание: даже после добавления элемента в список идентификатор списка остается неизменным, поскольку список является изменяемым типом. Сначала мы создаем список и проверяем идентификатор:

>>> names = []

>>> id(name)

140310794682432

Теперь добавим в список строку. Здесь есть ряд моментов, заслуживающих внимания. Возвращаемое значение метода .append не выводится (то есть метод не возвращает новый список). Но если просмотреть переменную names, выясняется, что новое имя успешно занесено в список. Также идентификатор списка остался неизменным — список был успешно изменен:

>>> names.append("Fred")

>>> names

['Fred']

>>> id(name)

140310794682432

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

7.4. Использование IDLE

Приведенный пример было бы неплохо опробовать в IDLE (или в вашем любимом редакторе с интегрированным интерпретатором REPL). Так как IDLE включает REPL, вы можете ввести приведенный код и проанализировать его прямо в REPL. Однако вы также можете написать код, запустить его и проанализировать из REPL. Чтобы опробовать эту возможность, откройте новый файл и включите в него следующий код:

name = "Matt"

first = name

age = 1000

print(id(age))

age = age + 1

print(id(age))

names = []

print(id(names))

names.append("Fred")

print(id(names))

Сохраните код в файле с именем iden.py. Запустите файл. В IDLE для этого следует нажать клавишу F5. В REPL будут выведены четыре числа. Первые два будут разными; это говорит о том, что целое число является неизменяемым. Последние два числа совпадают. Это объясняется тем, что несмотря на изменение списка names идентификатор остался прежним. В общем-то в этом факте нет ничего принципиально нового.

Теперь самое интересное: если ввести в REPL команду dir(), она выведет список переменных. Вы увидите, что глобальные переменные из iden.py теперь стали доступны.

REPL в IDLE открывает доступ ко всем глобальным переменным. Вы можете просмотреть name и names и даже вызывать функции или методы — например, names.append("George").

Имея возможность изучить результаты только что выполненного кода, вы можете быстро проанализировать код и поэкспериментировать с ним. Опытные разработчики Python нередко пишут код в REPL, вставляют его в файл, снова запускают файл, пишут новый код в REPL и продолжают писать код таким способом.

513840.png 

Рис. 7.2. Попытка изменить целое число неизбежно приводит к созданию нового целого числа. Целые числа неизменяемы, и их значение должно создаваться заново

513880.png 

Рис. 7.3. При присоединении объекта к списку изменяется значение списка. При добавлении и удалении элементов идентификатор списка не изменяется

7-4.tiff 

Рис. 7.4. Выполнение кода и анализ результатов в REPL в IDLE. Если вы не используете IDLE, узнайте, как это делается в вашем редакторе

7.5. Итоги

В Python нет ничего, кроме объектов. Объекты обладают тремя свойствами:

• Тип — определяет класс для объекта.

• Значение — данные, содержащиеся в объекте. Проверяя два объекта на равенство (==), вы сравниваете их значения.

• Идентификатор — уникальный идентификатор объекта. В версии Python на сайте идентификатор фактически определяет местонахождение объекта в памяти, которое является уникальным значением. Когда вы проверяете два объекта на тождественность (оператором is), вы проверяете, совпадают ли их идентификаторы.

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

7.6. Упражнения

1. Создайте переменную, которая указывает на число с плавающей точкой. Проанализируйте идентификатор, тип и значение этого числа. Обновите переменную, увеличив ее на 20. Снова проанализируйте идентификатор, тип и значение. Изменился ли идентификатор? Изменилось ли значение?

2. Создайте переменную, которая указывает на пустой список. Проанализируйте идентификатор, тип и значение списка. Присоедините к списку число 300. Снова проанализируйте идентификатор, тип и значение. Изменился ли идентификатор? Изменилось ли значение?

Назад: 6. Переменные
Дальше: 8. Числа