
Если вы когда-нибудь работали с такими низкоуровневыми языками, как С или С++, то наверняка слышали про указатели. Они позволяют сильно повышать эффективность разных кусков кода. Но также они могут запутывать новичков — и даже опытных разработчиков — и приводить к багам управления памятью. А есть ли указатели в Python, можно их как-то эмулировать?
Указатели широко применяются в С и С++. По сути, это переменные, которые содержат адреса памяти, по которым находятся другие переменные. Чтобы освежить знания об указателях, почитайте этот обзор.
Благодаря этой статье вы лучше поймёте модель объектов в Python и узнаете, почему в этом языке на самом деле не существуют указатели. На случай, если вам понадобится сымитировать поведение указателей, вы научитесь эмулировать их без сопутствующего кошмара управления памятью.
С помощью этой статьи вы:
- Узнаете, почему в Python нет указателей.
- Узнаете разницу между переменными C и именами в Python.
- Научитесь эмулировать указатели в Python.
- С помощью
ctypesпоэкспериментируете с настоящими указателями.
Примечание: Здесь термин «Python» применяется к реализации Python на C, которая известна под названием CPython. Все обсуждения устройства языка справедливы для CPython 3.7, но могут не соответствовать последующим итерациям.
Почему в Python нет указателей?
Не знаю. Могут ли указатели существовать в Python нативно? Вероятно, но судя по всему, указатели противоречат понятию Zen of Python, потому чт�� провоцируют неявные изменения вместо явных. Нередко указатели довольно сложны, особенно для новичков. Более того, они подталкивают вас к неудачным решениям или к тому, чтобы сделать что-нибудь действительно опасное, вроде чтения из области памяти, откуда вам не следовало считывать.
Python старается абстрагировать от пользователя подробности реализации, например адреса памяти. Часто в этом языке упор делается на удобство использования, а не на скорость. Поэтому указатели в Python не имеют особого смысла. Но не переживайте, по умолчанию язык предоставляет вам некоторые преимущества использования указателей.
Чтобы разобраться с указателями в Python, давайте кратко пройдёмся по особенностями реализации языка. В частности, вам нужно понять:
- Что такое изменяемые и неизменяемые объекты.
- Как устроены переменные/имена в Python.
Держитесь за свои адреса памяти, поехали!
Объекты в Python
Всё в Python является объектами. Например, откройте REPL и посмотрите, как используется
isinstance():>>> isinstance(1, object)
True
>>> isinstance(list(), object)
True
>>> isinstance(True, object)
True
>>> def foo():
... pass
...
>>> isinstance(foo, object)
TrueЭтот код демонстрирует, что всё в Python — на самом деле объекты. Каждый объект содержит как минимум три вида данных:
- Счётчик ссылок.
- Тип.
- Значение.
Счётчик ссылок используется для управления памятью. Подробно об этом управлении написано в Memory Management in Python. Тип используется на уровне CPython для обеспечения типобезопасности в ходе исполнения (runtime). А значение — это фактическое значение, ассоциированное с объектом.
Но не все объекты одинаковы. Есть одно важное отличие: объекты бывают изменяемые и неизменяемые. Понимание этого различия между типами объектов поможет вам лучше осознать первый слой луковицы, которая называется «указатели в Python».
Изменяемые и неизменяемые объекты
В Python есть два типа объектов:
- Неизменяемые объекты (не могут быть изменены);
- Изменяемые объекты (могут быть изменены).
Осознание этой разницы — первый ключ к путешествию по миру указателей в Python. Вот характеристика неизменяемости некоторых популярных типов:
| Тип |
Неизменяемый? |
|---|---|
| int |
Да |
| float |
Да |
| bool |
Да |
| complex |
Да |
| tuple |
Да |
| frozenset |
Да |
| str |
Да |
| list |
Нет |
| set |
Нет |
| dict |
Нет |
Как видите, многие из часто используемых примитивных типов являются неизменяемыми. Проверить это можно, написав кое-какой код на Python. Вам понадобится два инструмента из стандартной библиотеки:
id()возвращает адрес памяти объекта;
isвозвращаетTrue, если и только если два объекта имеют одинаковый адрес памяти.
Можете прогнать этот код в REPL-окружении:
>>> x = 5
>>> id(x)
94529957049376Здесь мы присвоили переменной
x значение 5. Если вы попробуете изменить значение с помощью сложения, то получите новый объект:>>> x += 1
>>> x
6
>>> id(x)
94529957049408Хотя может показаться, что этот код просто меняет значение
x, но на самом деле вы получаете в качестве ответа новый объект.Тип
str тоже неизменяем:>>> s = "real_python"
>>> id(s)
140637819584048
>>> s += "_rocks"
>>> s
'real_python_rocks'
>>> id(s)
140637819609424И в этом случае
s после операции += получает другой адрес памяти.Бонус: Оператор
+= преобразовывается в различные вызовы методов.Для некоторых объектов, таких как список,
+= преобразует в __iadd__() (локальное добавление). Оно изменит себя и вернёт тот же ID. Однако у str и int нет этих методов, и в результате будет вызываться __add__() вместо __iadd__().Подробнее об этом рассказывается в документации по моделям данных Python.
При попытке напрямую изменить строковое значение
s мы получим ошибку:>>> s[0] = "R"Обратная трассировка (последними отображаются самые свежие вызовы):
File "<stdin>", line 1, in <mоdule>
TypeError: 'str' object does not support item assignmentПриведённый выше код сбоит и Python сообщает, что
str не поддерживает это изменение, что соответствует определению неизменяемости типа str.Сравните с изменяемым объектом, например, со списком:
>>> my_list = [1, 2, 3]
>>> id(my_list)
140637819575368
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]
>>> id(my_list)
140637819575368Этот код демонстрирует основное различие между двумя типами объектов. Изначально у
my_list есть ID. Даже после добавления к списку 4, my_list всё ещё имеет тот же ID. Причина в том, что тип list является изменяемым.Вот ещё одна демонстрация изменяемости списка с помощью присваивания:
>>> my_list[0] = 0
>>> my_list
[0, 2, 3, 4]
>>> id(my_list)
140637819575368В этом коде мы изменили
my_list и задали ему в качестве первого элемента 0. Однако список сохранил тот же ID после этой операции. Следующим шагом на нашем пути к познанию Python будет исследование его экосистемы.Разбираемся с переменными
Переменные в Python в корне отличаются от переменных в C и C++. По сути, их просто нет в Python. Вместо переменных здесь имена.
Это может звучать педантично, и по большей части так оно и есть. Чаще всего можно воспринимать имена в Python в качестве переменных, но необходимо понимать разницу. Это особенно важно, когда изучаешь такую непростую тему, как указатели.
Чтобы вам было проще разобраться, давайте посмотрим, как работают переменные в С, что они представляют, а затем сравним с работой имён в Python.
Переменные в C
Возьмём код, который определяет переменную
x:int x = 2337;Исполнение это короткой строки проходит через несколько различных этапов:
- Выделение достаточного количества памяти для числа.
- Присвоение этому месту в памяти значения
2337.
- Отображение, что
xуказывает на это значение.
Упрощённо память может выглядеть так:

Здесь переменная
x имеет фальшивый адрес 0x7f1 и значение 2337. Если позднее вам захочется изменить значение x, можете сделать так:x = 2338;Этот код присваивает переменной
x новое значение 2338, тем самым перезаписывая предыдущее значение. Это означает, что переменная x изменяема. Обновлённая схема памяти для нового значения:
Обратите внимание, что расположение
x не поменялось, только само значение. Это важно. Нам это говорит о том, что x — это место в памяти, а не просто имя.Можно также рассматривать этот вопрос в рамках концепции владения. С одной стороны,
x владеет местом в памяти. Во-первых, x — это пустая коробка, которая может содержать лишь одно число (integer), в котором могут храниться целочисленные значения.Когда вы присваиваете
x какое-то значение, вы тем самым помещаете значение в коробку, принадлежащую x. Если вы хотите представить новую переменную y, то можете добавить такую строку:int y = x;Этот код создаёт новую коробку под названием
y и копирует в неё значение из x. Теперь схема памяти выглядит так:
Обратите внимание на новое местоположение
y — 0x7f5. Хотя в y и было скопировано значение x, однако переменная y владеет новым адресом в памяти. Следовательно, вы можете перезаписывать значение y, не влияя на x:y = 2339;Теперь схема памяти выглядит так:

Повторюсь: вы изменили значение
y, но не местоположение. Кроме того, вы никак не повлияли на исходную переменную x. С именами в Python совершенно иная ситуация.
Имена в Python
В Python нет переменных, вместо них имена. Вы можете на своё усмотрение использовать термин «переменные», однако важно знать разницу между переменными и именами.
Давайте возьмём эквивалентный код из вышеприведённого примера на С и напишем его на Python:
>>> x = 2337Как и в C, в ходе исполнения этого код проходит несколько отдельных этапов:
- Создаётся PyObject.
- Числу для PyObject’а присваивается typecode.
2337присваивается значение для PyObject’а.
- Создаётся имя
x. xуказывает на новый PyObject.- Счётчик ссылок PyObject’а увеличивается на 1.
Примечание: PyObject — не то же самое, что объект в Python, эта сущность характерна для CPython и представляет базовую структуру всех объектов Python.
PyObject определяется как C-структура, так что если вы удивляетесь, почему нельзя напрямую вызвать typecode или счётчик ссылок, то причина в том, что у вас нет прямого доступа к структурам. Вызовы методов вроде sys.getrefcount() могут помочь получить какие-то внутренние вещи.
Если говорить о памяти, то это может выглядеть таким образом:

Здесь схема памяти сильно отличается от схемы в С, показанной выше. Вместо того, чтобы
x владел блоком памяти, в котором хранится значение 2337, свежесозданный объект Python владеет памятью, в которой живёт 2337. Python-имя x не владеет напрямую каким-либо адресом в памяти, как С-переменная владеет статической ячейкой.Если хотите присвоить
x новое значение, попробуйте такой код:>>> x = 2338Поведение системы будет отличаться от того, что происходит в С, но будет не слишком сильно отличаться от исходной привязки (bind) в Python.
В этом коде:
- Создаётся новый PyObject.
- Числу для PyObject’а присваивается typecode.
2присваивается значение для PyObject’а.
xуказывает на новый PyObject.
- Счётчик ссылок нового PyObject увеличивается на 1.
- Счётчик ссылок старого PyObject уменьшается на 1.
Теперь схема памяти выглядит так:

Эта иллюстрация демонстрирует, что
x указывает на ссылку на объект и не владеет областью памяти, как раньше. Также вы видите, что команда x = 2338 является не присваиванием, а, скорее, привязкой (binding) имени x к ссылке.Кроме того, предыдущий объект (содержавший значение
2337) теперь находится в памяти со счётчиком ссылок, равным 0, и будет убран сборщиком мусора.Вы можете ввести новое имя
y, как в примере на С:>>> y = xВ памяти появится новое имя, но не обязательно новый объект:

Теперь вы видите, что новый Python-объект не создан, создано только новое имя, которое указывает на тот же объект. Кроме того, счётчик ссылок объекта увеличился на 1. Можете проверить эквивалентность идентичности объектов, чтобы подтвердить их одинаковость:
>>> y is x
TrueЭтот код показывает, что
x и y являются одним объектом. Но не ошибитесь: y всё ещё является неизменяемым. Например, вы можете выполнить с y операцию сложения:>>> y += 1
>>> y is x
FalseПосле вызова сложения, вам вернётся новый Python-объект. Теперь память выглядит так:

Был создан новый объект, и
y теперь указывает на него. Любопытно, что точно такое же конечное состояние мы получили бы, если напрямую привязали y к 2339:>>> y = 2339После этого выражения мы получим такое конечное состояние памяти, как и при операции сложения. Напомню, что в Python вы не присваиваете переменные, а привязываете имена к ссылкам.
Об интернированных (intern) объектах в Python
Теперь вы понимаете, как создаются новые объекты в Python и как к ним привязываются имена. Пришло время поговорить об интернированных (interned) объектах.
У нас есть такой Python-код:
>>> x = 1000
>>> y = 1000
>>> x is y
TrueКак и раньше,
x и y являются именами, указывающими на один и тот же Python-объект. Но это объект, содержащий значение 1000, не может всегда иметь одинаковый адрес памяти. Например, если вы сложили два числа и получили 1000, то получите другой адрес:>>> x = 1000
>>> y = 499 + 501
>>> x is y
FalseНа этот раз строка
x is y возвращает False. Если вас это смутило, не беспокойтесь. Вот что происходит при исполнении этого кода:- Создаётся Python-объект (
1000).
- Ему присваивается имя
x.
- Создаётся Python-объект (
499).
- Создаётся Python-объект (
501).
- Эти два объекта складываются.
- Создаётся новый Python-объект (
1000).
- Ему присваивается имя
y.
Технические пояснения: описанные шаги имеют место только в том случае, когда этот код исполняется внутри REPL. Если вы возьмёте приведённый пример, вставите в файл и запустите его, то строка
x is y вернёт True.Причина в сообразительности компилятора CPython, который старается выполнить peephole-оптимизации, помогающие по мере возможности экономить шаги исполнения кода. Подробности вы можете найти в исходном коде peephole-оптимизатора CPython.
Но разве это не расточительно? Ну да, но эту цену вы платите за все замечательные преимущества Python. Вам не нужно думать об удалении подобных промежуточных объектов, и даже не нужно знать об их существовании! Прикол в том, что эти операции выполняются относительно быстро, и вы бы о них не узнали до этого момента.
Создатели Python мудро подметили эти накладные расходы и решили сделать несколько оптимизаций. Их результатом является поведение, которое может удивить новичков:
>>> x = 20
>>> y = 19 + 1
>>> x is y
TrueВ этом примере почти такой же код, как и выше, за исключением того, что мы получаем
True. Всё дело в интернированных (interned) объектах. Python предварительно создаёт в памяти определённое подмножество объектов и хранит их в глобальном пространстве имён для повседневного использования.Какие объекты зависят от реализации Python? В CPython 3.7 интернированными являются:
- Целые числа в диапазоне от
-5до256.
- Строки, содержащие только ASCII-буквы, цифры или знаки подчёркивания.
Так сделано потому, что эти переменные очень часто используются во многих программах. Интернируя, Python предотвращает выделение памяти для постоянно используемых объектов.
Строки размером меньше 20 символов и содержащие ASCII-буквы, цифры или знаки подчёркивания будут интернированы, поскольку предполагается, что они будут применяться в качестве идентификаторов:
>>> s1 = "realpython"
>>> id(s1)
140696485006960
>>> s2 = "realpython"
>>> id(s2)
140696485006960
>>> s1 is s2
TrueЗдесь
s1 и s2 указывают на один и тот же адрес в памяти. Если бы мы вставили не ASCII-букву, цифру или знак подчёркивания, то получили бы другой результат:>>> s1 = "Real Python!"
>>> s2 = "Real Python!"
>>> s1 is s2
FalseВ этом примере использован восклицательный знак, поэтому строки не интернированы и являются разными объектами в памяти.
Бонус: Если хотите, чтобы эти объекты ссылались на один и тот же интернированный объект, то можете воспользоваться
sys.intern(). Один из способов применения этой функции описан в документации:Интернирование строк полезно для небольшого повышения производительности при поиске по словарю: если ключи в словаре и искомый ключ интернированы, то сравнение ключей (после хэширования) может выполняться с помощью сравнения указателей, а не строк. (Источник)
Интернированные объекты часто путают программистов. Просто запомните, что если начнёте сомневаться, то всегда можете воспользоваться
id() и is для определения эквивалентности объектов.Эмулирование указателей в Python
Тот факт, что указатели в Python отсутствуют нативно, не означает, что вы не можете воспользоваться преимуществами применения указателей. На самом деле есть несколько способов эмулирования указателей в Python. Здесь мы рассмотрим два из них:
- Применение в качестве указателей изменяемых типов.
- Применение специально подготовленных Python-объектов.
Применение в качестве указателей изменяемых типов
Вы уже знаете, что такое изменяемые типы. Именно благодаря их изменяемости мы можем эмулировать поведение указателей. Допустим, нужно реплицировать этот код:
void add_one(int *x) {
*x += 1;
}Этот код берёт указатель на число (
*x) и инкрементирует значение на 1. Вот основная функция для исполнения кода:#include <stdiо.h>
int main(void) {
int y = 2337;
printf("y = %d\n", y);
add_one(&y);
printf("y = %d\n", y);
return 0;
}В приведённом фрагменте мы присвоили
y значение 2337, вывели на экран текущее значение, увеличили его на 1, а затем вывели новое значение. На экране появляется:y = 2337
y = 2338Один из способов репликации этого поведения в Python — использовать изменяемый тип. Например, применить список и изменить первый элемент:
>>> def add_one(x):
... x[0] += 1
...
>>> y = [2337]
>>> add_one(y)
>>> y[0]
2338Здесь
add_one(x) обращается к первому элементу и увеличивает его значение на 1. Применение списка означает, что в результате мы получим изменённое значение. Так значит в Python существуют указатели? Нет. Описанное поведение стало возможным потому, что список — это изменяемый тип. Если вы попытаетесь использовать кортеж, то получите ошибку:>>> z = (2337,)
>>> add_one(z)Обратная трассировка (последними идут самые свежие вызовы):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in add_one
TypeError: 'tuple' object does not support item assignmentЭтот код демонстрирует неизменяемость кортежа, поэтому он не поддерживает присваивание элементов.
list не единственный изменяемый тип, указатели части эмулируются и с помощью dict.Допустим, у вас есть приложение, которое должно отслеживать возникновение интересных событий. Это можно сделать с помощью создания словаря и использования одного из его элементов в качестве счётчика:
>>> counters = {"func_calls": 0}
>>> def bar():
... counters["func_calls"] += 1
...
>>> def foo():
... counters["func_calls"] += 1
... bar()
...
>>> foo()
>>> counters["func_calls"]
2В этом примере словарь использует счётчики для отслеживания количества вызовов функции. После вызова
foo() счётчик увеличился на 2, как и ожидалось. И всё благодаря изменяемости dict.Не забывайте, это лишь эмуляция поведения указателя, оно никак не связано с настоящими указателями в C и C++. Можно сказать, эти операции обходятся дороже, чем если бы они выполнялись в C или C++.
Использование объектов Python
dict — прекрасный способ эмулирования указателей в Python, но иногда бывает утомительно помнить, какое имя ключа вы использовали. Особенно, если вы применяете словарь в разных частях приложения. Здесь может помочь настраиваемый класс Python.Допустим, вам нужно отслеживать метрики в приложении. Отличный способ абстрагироваться от раздражающих подробностей — это создать класс:
class Metrics(object):
def __init__(self):
self._metrics = {
"func_calls": 0,
"cat_pictures_served": 0,
}В этом коде определён класс
Metrics. Он всё ещё использует словарь для хранения актуальных данных, которые лежат в переменной члена _metrics. Это даст вам требуемую изменяемость. Теперь нужно лишь получить доступ к этим значениям. Можно сделать это с помощью свойств:class Metrics(object):
# ...
@property
def func_calls(self):
return self._metrics["func_calls"]
@property
def cat_pictures_served(self):
return self._metrics["cat_pictures_served"]Здесь мы используем @property. Если вы не знакомы с декораторами, то почитайте статью Primer on Python Decorators. В данном случае декоратор
@property позволяет обратиться к func_calls и cat_pictures_served, как если бы они были атрибутами:>>> metrics = Metrics()
>>> metrics.func_calls
0
>>> metrics.cat_pictures_served
0То, что вы можете обратиться к этим именам как к атрибутам, означает, что вы абстрагированы от факта, что эти значения хранятся в словаре. К тому же вы делаете имена атрибутов более явными. Конечно, у вас должна быть возможность увеличивать значения:
class Metrics(object):
# ...
def inc_func_calls(self):
self._metrics["func_calls"] += 1
def inc_cat_pics(self):
self._metrics["cat_pictures_served"] += 1Мы ввели два новых метода:
inc_func_calls()inc_cat_pics()
Они меняют значения в словаре
metrics. Теперь у вас есть класс, который можно изменить так же, как и указатель:>>> metrics = Metrics()
>>> metrics.inc_func_calls()
>>> metrics.inc_func_calls()
>>> metrics.func_calls
2Вы можете обращаться к
func_calls и вызывать inc_func_calls() в разных частях приложений и эмулировать указатели в Python. Это полезно в ситуациях, когда у вас есть что-то вроде metrics, что нужно часто использовать и обновлять в разных частях приложений.Примечание: В данном случае, явное создание
inc_func_calls() и inc_cat_pics() вместо использования @property.setter не даёт пользователям задавать эти значения произвольному int, или неправильное значение вроде словаря.Вот полный исходный код класса
Metrics:class Metrics(object):
def __init__(self):
self._metrics = {
"func_calls": 0,
"cat_pictures_served": 0,
}
@property
def func_calls(self):
return self._metrics["func_calls"]
@property
def cat_pictures_served(self):
return self._metrics["cat_pictures_served"]
def inc_func_calls(self):
self._metrics["func_calls"] += 1
def inc_cat_pics(self):
self._metrics["cat_pictures_served"] += 1Реальные указатели с помощью ctypes
Может быть, всё-таки есть указатели в Python, особенно в CPython? С помощью встроенного модуля ctypes можно создать настоящие указатели, как в C. Если вы не знакомы с ctypes, можете почитать статью Extending Python With C Libraries and the «ctypes» Module.
Вам это может понадобиться в тех случаях, когда нужно вызвать библиотеку С, которой необходимы указатели. Вернёмся к упомянутой выше С-функции
add_one():void add_one(int *x) {
*x += 1;
}Напомню, что этот код увеличивает значение
x на 1. Чтобы им воспользоваться, сначала скомпилируем код в общий (shared) объект. Будем считать, что наш файл хранится в add.c, сделать это можно с помощью gcc:$ gcc -c -Wall -Werror -fpic add.c
$ gcc -shared -o libadd1.so add.oПервая команда компилирует исходный файл C в объект
add.o. Вторая команда берёт этот несвязанный объект и создаёт общий объект libadd1.so.libadd1.so должен лежать в вашей текущей директории. Можете с помощью ctypes загрузить его в Python:>>> import ctypes
>>> add_lib = ctypes.CDLL("./libadd1.so")
>>> add_lib.add_one
<_FuncPtr object at 0x7f9f3b8852a0>Код ctypes.CDLL возвращает объект, который представляет общий объект
libadd1. Поскольку в нём вы определили add_one(), вы можете обращаться к этой функции, как если бы это был любой другой Python-объект. Но прежде чем вызывать функцию, нужно определить её сигнатуру. Так Python будет знать, что вы передаёте функции правильный тип.В нашем случае сигнатурой функции является указатель на число, ctypes позволит задать это с помощью такого кода:
>>> add_one = add_lib.add_one
>>> add_one.argtypes = [ctypes.POINTER(ctypes.c_int)]Здесь мы задаём сигнатуру функции, чтобы удовлетворить ожиданиям C. Теперь, если попробуем вызвать этот код с неправильным типом, то вместо непредсказуемого поведения получим красивое предупреждение:
>>> add_one(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ctypes.ArgumentError: argument 1: <class 'TypeError'>: \
expected LP_c_int instance instead of intPython бросает ошибку и объясняет, что
add_one() хочет получить указатель, а не просто целое число. К счастью, в ctypes есть способ передавать указатели таким функциям. Сначала объявим целое число в стиле С:>>> x = ctypes.c_int()
>>> x
c_int(0)Здесь мы создали целое число
x со значением 0. ctypes предоставляет удобную функцию byref(), которая позволяет передавать переменную по ссылке.Примечание: Словосочетание по ссылке является антонимом передаче переменной по значению.
При передаче по ссылке вы передаёте ссылку на исходную переменную, поэтому изменения будут отражены и на ней. При передаче по значению вы получаете копию исходной переменной, и изменения эту исходную переменную уже не затрагивают.
Для вызова
add_one() можете использовать этот код:>>> add_one(ctypes.byref(x))
998793640
>>> x
c_int(1)Отлично! Ваше число увеличилось на 1. Поздравляю, вы успешно использовали в Python настоящие указатели.
Заключение
Теперь вы лучше понимаете взаимосвязь между объектами Python и указателями. Хотя некоторые уточнения касательно имён и переменных выглядят проявлениями педантизма, однако понимание сути эти ключевых терминов улучшает ваше понимание механизма обработки переменных в Python.
Также мы узнали некоторые способы эмулирования указателей в Python:
- Использование изменяемых объектов в качестве указателей с низкими накладными расходами.
- Создание настраиваемых Python-объектов для простоты использования.
- Разлочивание настоящих указателей с помощью модуля ctypes.
Эти методы позволяют эмулировать указатели в Python без необходимости жертвовать предоставляемой языком безопасностью памяти.
