Строки в python неизменяемы что это значит
Неизменяемые типы данных
В этой лекции мы начнем знакомиться с основными типами данных в Python. О данных хорошо написано в книге «How to Design Programs»:
Every programming language comes with a language of data and a language of operations on data. The first language always provides some forms of atomic data; to represent the variety of information in the real world as data, a programmer must learn to compose basic data and to describe such compositions. Similarly, the second language provides some basic operations on atomic data; it is the programmer’s task to compose these operations into programs that perform the desired computations.
Типы данных можно разделить на изменяемые (mutable), то есть, значение которых можно изменить после создания, и неизменяемые (immutable), соответственно, значение которых нельзя изменить после создания. Эта лекция посвящена неизменяемым типам данных.
Все является объектом
В Python все является объектом («Everything is an Object»): числа, последовательности, функции, классы, модули и т.д. Каждый объект обладает уникальным идентификатором, который никогда не изменяется после создания объекта (в CPython идентификатором объекта является его адрес в памяти, который можно получить с помощью встроенной функции id() ), типом, который определяет «чем является объект» (числом, строкой, списком и т.д.) и какие действия над ним можно выполнять, а также значением.
Каждый объект «наследуется» от Си-структуры PyObject или PyVarObject для объектов переменной (variable) длинны (списки, кортежи и т.д.):
Связи между соответствующими структурами показаны на следующем рисунке:
Итак, если вы решили ввести свой тип, то он должен «наследоваться» от PyObject или PyVarObject с помощью макросов PyObject_HEAD и PyObject_VAR_HEAD :
Следует помнить, что макрос PyObject_HEAD должнен идти первым в структуре. Это связано с «наследованием», о котором говорилось ранее. Как утверждается в object.h :
Итак, PyObject и PyVarObject являются наиболее общими структурами для представления объектов в CPython, но пока мы не говорили о том как создаются новые объекты. В одной из последующих лекций мы вернемся к этому вопросу.
Целочисленный тип данных и числа с плавающей точкой
Без использования стандартной библиотеки языка нам доступны целые числа ( int ), вещественные числа ( float ) и комплексные числа ( complex ):
У каждого типа обычно есть «конструктор»:
Основные арифметические операции:
Взятие целой части и остатка от деления:
Для записи очень больших или очень маленьких чисел удобно использовать экспоненциальную форму записи чисел. Сравните:
И не стоит забывать про ошибки округления при работе с вещественными числами:
Длинная арифметика в Python
Структура PyLongObject отвечает за представление целых чисел:
Связи между соответствующими структурами показаны на следующем рисунке:
Представление произвольно больших целых чисел
Комментарий из исходных текстов по представлению целых чисел:
Конвертировать число обратно также достаточно просто:
Преобразования длинного целого в массив
Ниже приведен упрощенный вариант алгоритма представления произвольно больших чисел:
Если мы хотим убедиться, что нигде не ошиблись, то можем посмотреть на внутреннее представление целого числа с помощью модуля ctypes, который позволяет взаимодействовать с Си-кодом из Python:
Оптимизации
Давайте рассмотрим простой пример:
Следует иметь ввиду, что структура PyLongObject занимает не менее 28 байт для каждого целого числа, то есть в три раза больше чем требуется под 64-битное целое в языке C.
Выполнение арифметических операций
Базовые арифметические операции выполняются аналогично тому, как мы это делали когда-то в школе, с одним исключением: каждый элемент массива считается «цифрой».
Давайте рассмотрим вариант алгоритма сложения с переносом:
Замечание про Numpy и Pandas
В тех случаях, когда мы пользуемся библиотеками numpy/scipy/pandas и т.д., может произойти переполнение при работе с целыми числами, так как структуры, лежащие в основе этих библиотек, для более эффективного использования памяти, полагаются на соответствующие С-типы ограниченной точности 2 :
При вычислении среднего элементы массива сначала приводятся к типу float и переполнения не возникает:
Числа с плавающей точкой и стандарт IEEE-754
Вещественные числа в CPython представлены структурой PyFloatObject :
Легко заметить, что поле ob_fval это обычное вещественное число двойной точности. Все арифметические операции над вещественными числами в Python являются простыми обертками над соответствующими арифметическими операциями в Си, например, операция сложения определена следующим образом:
Следует помнить, что все вычисления в вещественных числах делаются компьютером с некоторой ограниченной точностью (см. стандарт IEEE-754), поэтому зачастую вместо «честных» ответов получаются приближенные (к этому надо быть готовым), например:
Если вы не понимаете почему мы не получили единицу, то попробуйте перевести число 0.1 в двоичную систему счисления:
В некоторых случаях на помощь может придти модуль fmath :
Булевый тип
Строки
Строки в Python версии 3 представляют собой последовательность символов Юникод (code point’ов). Если вы никогда не слышали про Юникод и кодировки или плохо представляете, что это такое, то советую прочитать исчерпывающую статью David C. Zentgraf из серии «Что каждый программист должен знать о. ».
Новую строку можно создать с помощью одинарных или двойных кавычек (можно использовать и тройные кавычки, но чаще они используются для документирования функций, классов, модулей):
Для строк определена операция сложения (конкантенации):
Строку можно разбить на список подстрок по заданному разделителю, который в свою очередь также является строкой:
Заканчивается ли строка данной подстрокой:
У строк (как и у большинства контейнеров) можно получить длину (число элементов в контейнере):
Можно обращаться к отдельным элементам строки, которые представляют собой строку из одного символа:
Строки являются неизменяемыми, то есть мы не можем изменить отдельный элемент строки:
И наконец мы можем брать подмножество (срез) элементов строки:
Мы рассмотрели лишь небольшую часть операций, которые можно выполнять над строками. Полный список операций вы всегда можете найти в документации.
Представление строк
Как уже было сказано, строки в Python являются Юникод строками. Для внтуреннего представления строк в Python, начиная с версии 3.3 (см. PEP-393), используются кодировки Latin-1 (1 байт на символ), UCS-2 (2 байта на символ) и UCS-4 (4 байта на символ). Упрощенно процесс определения кодировки следующий: когда необходимо создать строковый объект (тексты программ обычно в кодировке UTF-8), Python находит самый старший кодовый знак (code point) в строке и выбирает кодироку, в которой кодовый знак может быть представлен «как есть».
Опишем структуру PyASCIIObject с помощью модуля ctypes :
Создадим несколько строковых объектов:
Обратите внимание, что хотя длина строк и становится меньше, но занимаемый размер наоборот растет, так как используются разные кодировки для внутреннего представления. Об этом важно помнить, особенно когда вы работаете с большими объемами текстов, так как один символ эмодзи может увеличить занимаемый размер строки в 4 раза.
Почему для внутреннего представления строк не используется UTF-8? Кодировка UTF-8 подразумевает, что может использоваться варьируемое число байт (от одного до четырех) для кодирования одного символа. UTF-8 является оптимальной кодировкой с точки зрения хранения строк (то есть кодировка эффективна по памяти), но при обращении к отдельным элементам строки (при индексировании), необходимо пройтись по всем символам строки пока мы не дойдем до нужного символа. При фиксированном размере такой проблемы не возникает, для перехода к нужной позиции символа достаточно индекс умножить на размер кодового знака (1, 2 или 4 в зависимости от используемой кодировки). Тем не менее есть мнение, что индексация это не проблема.
Интернирование строк
Дополнительно про интернирование можно почитать тут и тут.
Для экономии памяти в Python реализовано интернирование строк (string interning). Давайте рассмотрим такой пример, пусть у нас есть два строковых объекта с одинаковым содержимым:
Хотя содержимое строк совпадает это два разных объекта. С другой стороны:
получим, что адреса s1 и s2 совпадают. Все строки длиной 0 или 1 интернированы, кроме того интернируются все строковые литералы, состоящие из символов латинского алфавита, цифр или нижнего подчеркивания, также интернируются имена переменных, функций, классов и т.д.
Если мы хотим интернировать строку, то следует воспользоваться функцией intern из модуля sys :
Использование интернирования строк гарантирует, что не будет создано двух одинаковых строковых объектов. Когда вы создаете второй объект с тем же значением, что и у существующего объекта, то вы получаете ссылку на уже существующий объект. Таким образом, интернирование строк позволяет экономить память и повышает скорость сравнения строк, путем сравнения их адресов (хешей), а не содержимого.
Кортежи
Последний неизменяемый тип, который мы рассмотрим в этой лекции это кортежи, которые фактически являются статическими массивами, то есть, имеют фиксированный размер, и представлены структурой PyTupleObject :
Рассмотрим простой пример создания кортежа из трех элементов:
Как уже было отмечено, важной особенностью кортежей является то, что это неизменяемая структура:
Когда мы говорим, что кортежи неизменяемые, то имеем ввиду, что мы не можем заменить один элемент кортежа на другой, но сам объект изменить мы можем:
Значительная часть материала про представление целых чисел взята из статьи Артема Голубина: Python Integer Implementation. ↩
Изменяемые vs. неизменяемые типы данных в Python
Python считается одним из самых удивительных языков программирования. Многие люди выбирают его в качестве первого языка из-за его элегантности и простоты. Благодаря широкому сообществу, избытку пакетов и согласованности синтаксиса, опытные профессионалы также используют Python. Тем не менее, существует одна вещь, которая раздражает как новичков, так и некоторых профессиональных разработчиков – объекты Python.
Изменяемые vs. неизменяемые объекты
Как известно, объектом в Python является абсолютно все, а каждый объект относится к какому-либо типу данных. Типы данных бывают изменяемые и неизменяемые (англ. mutable и immutable). К неизменяемым относятся целые числа (int), числа с плавающей запятой (float), булевы значения (bool), строки (str), кортежи (tuple). К изменяемым — списки (list), множества (set), байтовые массивы (byte arrays) и словари (dict).
Функции id() и type()
Разобраться с изменяемостью типов данных нам помогут встроенные функции и операторы Python.
Встроенный метод id() возвращает идентификатор объекта в виде целого числа. Это целое число обычно относится к месту хранения объекта в памяти. Встроенная функция type() возвращает тип объекта.
Неизменяемые типы данных
Давайте рассмотрим некоторые неизменяемые типы.
Целые числа (int)
Строки (str)
То же самое верно и для строкового типа данных. Мы не можем изменить существующую переменную, вместо этого мы должны создать новую с тем же именем.
Кортежи (tuple)
Давайте разберем кортежи. Мы определили кортеж с 4 значениями. Воспользуемся функцией id() для вывода его адреса. Если мы захотим изменить значение первого элемента, то получим ошибку TypeError. Это означает, что кортеж не поддерживает присвоение или обновление элементов.
С другой стороны, мы можем обновить весь кортеж, задав его с нуля. После этого мы увидим новые значения элементов кортежа и новый адрес.
Числа с плавающей запятой (float)
Если же мы обновим float, переопределив его, то при вызове получим новое значение и новый адрес.
Изменяемые типы данных
Теперь давайте рассмотрим некоторые изменяемые типы.
Списки (list)
Определим список с именем x и добавим в него некоторые значения. После этого обновим список: присвоим новое значение элементу с индексом 1. Можем заметить, что операция успешно выполнилась.
Вышеописанные действия являются простым и базовым примером модификации. Чтобы проверить изменчивость на более глубоком уровне, давайте рассмотрим тот же пример с небольшими изменениями.
Теперь добавим новое значение к списку x и проверим обновленный вывод.
Словари (dict)
Словари — часто используемый тип данных в Python. Давайте посмотрим на их изменчивость.
Определим словарь под именем dict с тремя ключами и их значениями. Когда мы распечатаем его, отобразится все его содержимое. Можно распечатать каждое значение словаря отдельно, а также использовать ключи вместо индексов. Подробнее о добавлении элементов в словарь вы можете узнать тут.
Списки и кортежи: наглядный пример изменяемых и неизменяемых объектов
Давайте по отдельности определим список и кортеж. Убедимся, что в кортеже есть значение типа список, а в списке есть значение типа кортеж.
Нулевой элемент кортежа – список. Давайте попробуем изменить какой-то из элементов списка, указав его индекс. Например, можно поменять в нулевом элементе кортежа (т.е. в списке) нулевой элемент. Нам успешно удается это сделать, потому что список – изменяемый объект, даже если он находится в кортеже.
Если же, наоборот, кортеж находится в списке, то вы не сможете поменять элемент этого кортежа, хотя он и находится в изменяемом списке. Ведь сам кортеж неизменяем. Поэтому такие преобразования невозможны.
Заключение
Мы разобрали различия между изменяемым и неизменяемым объектами в Python. Стоит понимать, что всё в Python называется объектами. И главное различие между ними – являются они изменяемыми или неизменяемыми.
Python 3: изменяемый, неизменяемый…
Многие из нас считают Python прекрасным языком программирования. В нем легко разобраться, он очень хорошо читается, а код на нем легко поддерживать. Но основная причина простоты этого языка состоит в том, что под капотом у него есть очень много всего. И хотя многие люди просто знают, что в языке есть и сложные вещи, и им этого достаточно, понимание тонкостей поможет вам избежать появления многих багов в ваших программах. Цель этой статьи — познакомить вас с этими тонкостями.
Объекты
Давайте начнем с краеугольного камня Python — с объектов. Объектом является буквально все. Ваш модуль это объект, функция — объект, число — тоже объект. Объекты это просто экземпляры классов.
Чтобы понять эту концепцию, мы можем представить, что объекты — это отдельные люди, а классы — это группы, к которым эти люди принадлежат. Скажем, все мы — люди (класс human), но я — уникальный индивид (экземпляр класса human).
В Python все является объектом. Каждый объект имеет собственный тип данных и внутреннее состояние (данные). Давайте для начала уясним, как данные хранятся в памяти. Для этого рассмотрим пару примеров.
Пример 1
Пример 2
Идентичность и тип
Функция id() возвращает идентификатор указанной вами переменной. Каждая переменная в Python ссылается на какой-нибудь объект, а идентификатор переменной это целое число, «привязанное» к конкретному объекту. В реализации CPython это число — адрес объекта в памяти. Идентификатор объекта позволяет разграничить случаи, когда переменные идентичны, и когда они ссылаются на один и тот же объект. Для определения идентичности мы можем использовать «==», а «is» используется для определения того, указывают ли переменные на один и тот же объект.
Но все станет куда более странно, если мы сделаем вот так:
Вероятно, вы думаете: «Погодите, погодите! Что?! Ведь пару секунд назад, когда мы рассматривали списки, я понял, что они разные. А эти тогда почему одинаковые?» Чтобы ответить на этот вопрос, мы должны углубиться в предмет изменяемости.
Изменяемые объекты
Итак, что же такое изменяемость (мутабельность)? Выражаясь простым языком, это способность мутировать — или изменять объект. К изменяемым (мутабельным) типам в Python относятся списки, множества, словари и bytearrays (массивы байтов). Давайте рассмотрим пример с нашим старым добрым списком. Мы можем добавлять в него элементы:
Мы можем удалять из него элементы:
Как уже говорилось, в Python все является объектом. Поэтому каждый элемент в списке это тоже объект. Если мы приглядимся, все опять станет очень странным:
Почему идентификатор l[1] изменился? Идентификатор всего списка остался неизменным, хотя мы изменили его значения, но почему же изменился идентификатор его элемента? Потому что числа и строки неизменяемы (не мутабельны).
Неизменяемые объекты
Неизменяемость это противоположность изменяемости (логично). Если объект неизменяемый, это значит, что вы не можете менять его содержимое. К встроенным неизменяемым типам относятся целые числа, числа с плавающей точкой, комплексные числа, строки, кортежи, frozenset-ы и байты.
В приведенном выше примере объект список изначально содержал ссылки на объекты «1», «2» и «3». Хотя мы не можем изменить сам объект (цифру 2), можно изменить список за счет изменения хранящейся в нем ссылки. Теперь список содержит ссылку на объект «Hello». Другими словами, произошло переназначение. И хотя увеличивать неизменяемый объект нельзя, мы можем сделать так:
Кажется, что мы добавили «World» к «Hello», но на самом деле произошло примерно следующее:
Было вычислено значение s («Hello») плюс “ World”, и переменная s получила ссылку на новый объект. Похожий процесс происходит при добавлении чисел:
Но если вы попытаетесь изменить элемент в неизменяемом наборе, интерпретатор возбудит исключение:
Аналогично — хотя удалить элемент в неизменяемом объекте нельзя, вы можете взять срез объекта, в результате чего вернется новый объект, содержащий нужные значения:
Поскольку Python должен создавать отдельный объект для каждого уникального неизменяемого значения (что занимает много памяти), интерпретатор разумным образом оптимизирует создание объектов. Эта оптимизация происходит за счет использования одинаковых ссылок для неизменяемых объектов, таких как строки:
Преаллокация в Python
А теперь домашнее задание для вас:
Почему это важно?
При присваивании значений переменным очень важно понимать разницу между изменяемыми и неизменяемыми объектами. Изменяемые объекты имеют пару тузов в рукаве. Мы уже рассматривали этот пример:
А что, если мы хотим создать копию объекта, чтобы при внесении изменений не волноваться о том, что это затронет оригинал? Мы можем взять срез списка, при этом вернется новый объект (так же, как когда мы брали срез неизменяемого объекта):
Добавление элементов в списки тоже может быть хитрой штукой. В Python то, что находится в левой части выражения присваивания, получает ссылку на все, что вычисляется в правой. Поэтому:
Мы видим, что объект остался прежним. Это замещающее присваивание — эквивалент добавления элементов в список. Но поскольку иммутабельные объекты не могут быть изменены, выражения a += b и a = a + b будут работать одинаково и аналогично нашему примеру «Hello World».
Передача аргументов в функции
В других языках программирования переменные часто передаются одним из двух способов:
Но Python в этом плане уникален. Python передает ссылку на объект. Аналогично тому как мы делали переменную y псевдонимом переменной x ( x = y ), при передаче переменной в функцию аргумент функции становится псевдонимом для ссылки на объект.
Это означает, что если внутри функции произойдет новое присваивание, это не изменит оригинальную переменную, переданную в функцию. Поскольку иммутабельные типы не могут быть изменены, никакое изменение переменной внутри функции не сохранится:
Но поскольку списки мутабельны, их содержимое изменять можно:
Обратите внимание, что ссылка на список остается той же на протяжении всей программы. Тем не менее, если бы мы переприсвоили значение l внутри функции, это не затронуло бы исходный список.
Исключения в неизменяемости
Не все неизменяемые объекты на самом деле неизменяемы. Да, все очень запутанно, но сейчас разберемся.
Как уже говорилось, в Python контейнеры типа кортежей неизменяемы. Это значит, что значение tuple не может меняться после того, как кортеж создан. Но «значение» кортежа на самом деле является последовательностью имен с неизменяемыми привязками к объектам. Главное, что нужно понять, это что неизменяемы именно привязки, а не сами объекты.
Кортеж t содержит элементы с разными типами данных. Первый элемент — неизменяемая строка, а второй — изменяемый список. Сам кортеж неизменяемый (нет никаких методов для изменения его содержимого). Аналогично и строка неизменяема, потому что у строк нет никаких методов для их изменения. Но объект список имеет методы, с помощью которых его можно изменить, так что он изменяемый. Это мелочь, но важная: «значение» неизменяемого объекта не может меняться, но объекты, из которых он состоит, — могут.
Заключение
Понимание тонкостей языка может быть чрезвычайно полезным. Многие программисты не уделяют достаточно времени тому, чтобы хорошенько изучить языки, которыми пользуются на постоянной основе. Но близкое знакомство с Python — отличный способ избежать багов.
Кроме того, самодисциплина, позволяющая вам сесть и изучить, что происходит под капотом языка, — сама по себе отличная вещь, которая сослужит вам прекрасную службу в вашей программистской карьере.