Динамическая типизация

Следующий код python исполняется без ошибок.

a = 0               # a - переменная типа int
print(a, type(a))
a = 0.              # теперь a - переменная типа float
print(a, type(a))
a = "zero"          # теперь a - переменная типа str
print(a, type(a))
a = [0, 0., "zero"] # теперь a - переменная типа list
print(a, type(a))
0 <class 'int'>
0.0 <class 'float'>
zero <class 'str'>
[0, 0.0, 'zero'] <class 'list'>

Кажется, что переменная a меняет свой тип 3 раза. Как это возможно? Чтобы ответить на этот вопрос, вспомним, как это устроенно в C/C++.

Note

Функция type спрашивает у объекта его тип.

Статическая типизация в C/C++

Рассмотрим следующий код на языке C/C++.

int main(){
  char a;     // 1
  a = 42;     // 2
  return 0;
}

В примере объявляется переменная a типа char, а затем ей присваивается целочисленное значение 42.

  • При объявлении переменной выделяется блок оперативной памяти компьютера, достаточный для ее хранения (1 байт в данном случае). Эта память будет иметь фиксированный адрес (адрес переменной) &a1.

  • В следующей строке значение 42 типа int приводится к char и записывается в ячейку памяти, связанную с переменной a. Это возможно, потому что компилятору известно какого типа переменная a.

Итого, опуская технические подробности:

  • переменная соответствует области в памяти;

  • оператор присвоения = записывает значение справа от него в область памяти, именованную переменной слева от него.

Динамическая типизация в python

В python тип хранится не в переменной, а в самом объекте, а сама переменная всего лишь ссылается на объект (в python всё объекты), ничего не подозревая о типе этого самого объекта. На самом деле переменная в python представляет собой обертку над указателем C/C++, который всегда автоматически разыменовывается, а оператор присвоения = связывает переменную слева от него с объектом справа от него (изменяет значение указателя).

Note

В связи с этим, иногда термин “переменная” (variable) избегается в отношении python и заменяется терминами “имя” (name), “ссылка” (reference) или идентификатор.

Note

В python работать с адресами памяти напрямую невозможно. Единственное исключение составляет функция id, которая в некоторых реализациях возвращает адрес памяти (целое число), где хранится объект. Эта функция нужна только для того, чтобы всегда можно было выяснить, ссылаются ли два имени на один и тот же объект. Обратится по этому адресу средствами python невозможно.

Теперь рассмотрим аналогичный код в python.

a = 0       # 1
a = 42      # 2

Первая инструкция a=0. Сначала вычисляется значение выражения справа. Создается объект типа int содержащий в себе значение 0. Так как имени a до этого объявлено не было, то такое имя создаётся и связывается с объектом справа.

../_images/a0.svg

Следующая инструкция a = 42. Создаётся объект типа int, содержащий значение 42. Имя a связывается с новым объектом.

../_images/a42.svg

Расширим пример ещё одной командой a="s".

a = 0       # 1
a = 42      # 2
a = "s"     # 3

Новая инструкция a="s". Создаётся объект типа str, содержащий значение "s". Имя a связывается связывается с новым объектом, который имеет другой тип.

../_images/as.svg

Note

Иногда говорят, что тип объекта “живёт” в самом объекте, а не в “переменной”.