Какие типы данных являются неизменяемыми в python
Перейти к содержимому

Какие типы данных являются неизменяемыми в python

  • автор:

Изменяемые и неизменяемые объекты в Python

Все в Python – это объект. Каждый новичок должен сразу усвоить, что все объекты в Python могут быть либо изменяемыми (мутабельным), либо неизменяемыми (иммутабельным).

Давайте углубимся в детали. Поскольку все в Python является объектом, то каждая переменная является экземпляром какого-то класса (объектом). Когда объект инстанцируется, ему присваивается уникальный id. Тип объекта определяется во время выполнения и после этого не меняется, однако состояние может меняться, если оно изменяемое. Проще говоря, изменяемый объект можно изменить после его создания, а неизменяемый – нет.

Объекты встроенных типов, таких как int, float, bool, str, tuple, unicode, являются неизменяемыми. Объекты встроенных типов, таких как list, set, dict, являются изменяемыми. Самописные классы, как правило, изменяемые. Если есть потребность сымитировать неизменяемость, нужно переопределить методы установки и удаления атрибутов класса так, чтобы они вызывали исключения.

Возникает вопрос, как узнать, что наша переменная изменяемый или неизменяемый объект. Для этого нам нужно понять, зачем нужны функции id и type.

Id() и type()

Встроенная функция id() возвращается id объекта в виде целого числа. Число обычно коррелирует с местоположением объекта в памяти, но не всегда, поскольку все зависит от реализации Python и используемой платформы. Оператор is сравнивает id двух объектов.

Встроенная функция type() возвращает тип объекта. Давайте обратимся к примеру.

''' Example 1 ''' >>> x = "Holberton" >>> y = "Holberton" >>> id(x) 140135852055856 >>> id(y) 140135852055856 >>> print(x is y) '''comparing the types''' True ''' Example 2 ''' >>> a = 50 >>> type(a) >>> b = "Holberton" >>> type(b)

Теперь мы знаем, как сравнить две простые строковые переменные, чтобы определить их тип и id. С помощью этих двух функций мы можем узнать типы объектов связанных с переменными и их изменяемость/неизменяемость.

Изменяемые и неизменяемые объекты

Итак, как мы выяснили, изменяемый объект может изменять свое состояние или содержимое, а неизменяемый – нет.

Изменяемые объекты:

list, dict, set, byte array

Неизменяемые объекты:

int, float, complex, string, tuple, frozenset (неизменяемая версия set), bytes

Пример определения изменяемости объекта:

x = 10 x = y

Мы создаем объект типа int. Id x и y указывают на один и тот же объект:

id(x) == id(y) id(y) == id(10)

А теперь просто прибавим единицу:

x = x + 1
id(x) != id(y) id(x) != id(10)

Объект переменной x изменился. Объект 10 не может изменяться. Неизменяемые объекты не допускают изменений после создания.

А если объект изменяемый:

m = list([1, 2, 3]) n = m

Мы создаем объект типа list. Id переменных m и n привязаны к одному списку, в котором лежит коллекция из трех неизменяемых объектов типа int.

id(m) == id(n)

Удаление элемента из объекта списка изменяет сам объект:

m.pop()

Но id объекта не изменяется

id(m) == id(n)

Переменные m и n будут указывать на один и тот же объект списка после изменения. В объекте списка теперь лежит [1,2].

Итак, что же мы поняли из примеров выше?

  • Python обрабатывает изменяемые и неизменяемые объекты по-разному.
  • Доступ к неизменяемым объектам осуществляется быстрее, чем к изменяемым.
  • Изменяемые объекты отлично подойдут, если вам нужно менять размер объектов, например, list, dict и т.д.
  • Неизменяемые значения используются, когда вам нужно убедиться, что созданный вами объект никогда не будет меняться.
  • Неизменяемые объект принципиально дорого менять, поскольку для этого требуется создать копию, а изменяемые менять легко.

Исключения в неизменяемости

Не все неизменяемые объекты на самом деле являются неизменяемыми. Запутались? Позвольте мне объяснить.

Как мы говорили ранее, такие коллекции в Python, как кортежи, неизменяемы. То есть значение кортежа нельзя менять после его создания. Но значение кортежа на самом деле – это последовательность имен с неизменяемыми привязками к объектам. Главное, что нужно понимать, так это то, что сами «привязки» менять нельзя, а вот объекты на их концах – можно.

Рассмотрим кортеж t = (‘holberton’, [1, 2, 3])

Он содержит элементы разных типов данных, первый из которых – неизменяемая строка, а второй – изменяемый список. Сам со себе кортеж неизменяемый, то есть для него нет методов изменения содержимого. Строка также неизменяемая по той же причине. Но у списка есть такие методы, поэтому его можно изменить. Здесь тонкий момент, но не менее важный: «значение» неизменяемого объекта не может измениться, а вот его элементы могут.

Как объекты передаются в функции

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

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

def updateList(list1): list1 += [10] n = [5, 6] print(id(n)) # 140312184155336 updateList(n) print(n) # [5, 6, 10] print(id(n)) # 140312184155336

Как видно из примера выше, мы получили доступ к списку по ссылке, поэтому смогли изменить исходный список.

Давайте посмотрим на другой пример:

def updateNumber(n): print(id(n)) n += 10 b = 5 print(id(b)) # 10055680 updateNumber(b) # 10055680 print(b) # 5

В примере выше в функцию передается один и тот же объект, но значение переменных не меняется даже если их id одинаковые. Так работает передача по значению. Что же здесь происходит? Когда функция вызывает значение, передается только значение переменной, а не сам объект. Таким образом, переменная, ссылающаяся на объект, не изменяется, а сам объект изменяется, но только в пределах области видимости функции. Следовательно, изменение не видно «снаружи».

В завершение приглашаем всех на бесплатный урок, где поговорим про организацию рабочего места:

  • что такое IDE и какие IDE существуют;
  • как настроить самую полнофункциональную IDE для Python — PyCharm Community Edition;
  • версии Python, почему их так много;
  • как разрабатывать несколько проектов на одной машине (что нужно учитывать, зачем нужны виртуальные среды (venv) для проектов) и что это такое;
  • как запустить приложение в Docker-контейнере (зачем это делать, как это можно сделать, что нужно учесть).

В результате вы сможете организовать изолированную среду для разработки и запуска приложения с помощью venv и docker, узнаете почему существуют различные версии Python, и как организовать свое рабочее место, чтобы разрабатывать несколько различных проектов, использующих разные версии Python и сторонних пакетов, одновременно, и без боли.

Типы данных в Python: какие они бывают и как их различать

Виртуальное окружение в разработке на языке Python

Моё мнение про Python

Операторы в Python: какие они бывают, как работают и где используются

Основные аспекты изучения Python

В Python типы данных — это категории, которые определяют характеристики и поведение значений, использованных в программе. Python поддерживает различные типы данных, каждый из которых представляет определенный вид информации.

Базовые типы данных в Python

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

Числовые типы данных

— `int` (integer) — целые числа.

```python age = 25 count = 100 ```

— `float` (floating-point) — числа с плавающей точкой.

```python pi = 3.14 temperature = 98.6 ```

Строковый тип данных

— `str` (string) — последовательность символов, заключенных в кавычки.

```python name = "Alice" message = 'Привет, мир!' ```

Логический тип данных

— `bool` (boolean) — логические значения `True` (истина) и `False` (ложь).

```python is_student = True is_adult = False ```

Списки

— `list` — упорядоченная изменяемая коллекция элементов, которые могут быть разных типов.

```python numbers = [1, 2, 3, 4, 5] fruits = ['яблоко', 'банан', 'груша'] ```

Кортежи

— `tuple` — упорядоченная неизменяемая коллекция элементов, которые могут быть разных типов.

```python coordinates = (10, 20) rgb_color = (255, 0, 0) ```

— `dict` (dictionary) — неупорядоченная коллекция пар ключ-значение, позволяющая быстро находить значение по ключу.

```python person = grades = ```

Множества

— `set` — неупорядоченная коллекция уникальных элементов.

```python colors = fruits_set = set(['яблоко', 'банан', 'груша']) ```

Каждый из этих базовых типов данных имеет свои особенности и применение в программировании. От выбора правильного типа данных зависит эффективность и функциональность вашего кода.

�� Готовы устроиться на высокооплачиваемую работу программиста?��

��Стартуйте с Python Start — онлайн курс от Foxminded, который откроет двери в мир Python.

Числовые типы данных в Python

В Python существуют два основных числовых типа данных: `int` (целые числа) и `float` (числа с плавающей точкой). Оба типа представляют числовые значения, но имеют некоторые отличия в своих свойствах и использовании.

`int` (целые числа)

— `int` представляет целые числа без дробной части.

— Примеры: 1, -5, 1000, 0.

— Целые числа могут быть положительными, отрицательными или нулем.

— `int` не имеет ограничения по размеру и может представлять целые числа произвольной длины.

Примеры использования `int`:

```python age = 25 quantity = 100 ```

`float` (числа с плавающей точкой)

— `float` представляет числа с плавающей точкой, т.е. числа с дробной частью.

— Примеры: 3.14, -0.5, 2.0, 1.23e-5 (научная нотация).

— `float` используется для представления вещественных чисел и результатов арифметических операций, в которых присутствует дробная часть.

Примеры использования `float`:

```python pi = 3.14 temperature = 98.6 ```

Общие операции с числовыми типами данных

1. Арифметические операции: `+` (сложение), `-` (вычитание), `*` (умножение), `/` (деление), `**` (возведение в степень).

```python a = 10 b = 3 result_sum = a + b # 13 result_sub = a — b # 7 result_mul = a * b # 30 result_div = a / b # 3.33333. result_power = a ** b # 1000 ```

2. Преобразование типов.

— Иногда может потребоваться преобразовать числовой тип данных из `int` в `float` или наоборот.

```python x = 5 y = 2.5 int_x = int(y) # 2 float_y = float(x) # 5.0 ```

3. Округление чисел.

— В Python есть функции `round()` и `int()` для округления чисел.

```python number = 3.6 rounded_number = round(number) # 4 integer_part = int(number) # 3 ```

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

Неизменяемые типы данных в Python

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

Вот некоторые из основных неизменяемых типов данных в Python:

Числовые типы данных (`int`, `float`)

— Числовые типы данных в Python являются неизменяемыми. После создания переменной с числовым значением, это значение нельзя изменить.

```python x = 5 y = 3.14 x = 10 # Правильно: создается новый объект с новым значением y = y + 1 # Правильно: создается новый объект с новым значением ```

Строковый тип данных (`str`)

— Строки в Python также являются неизменяемыми. Когда создается строковая переменная, нельзя изменить ее символы напрямую.

```python message = "Привет" # Неправильно: нельзя изменить символы в строке message[0] = "п" # Правильно: создается новый объект с новым значением new_message = "п" + message[1:] ```

Кортежи (`tuple`)

— Кортежи являются неизменяемыми коллекциями элементов. После создания кортежа его элементы не могут быть изменены.

```python coordinates = (10, 20) # Неправильно: нельзя изменить элементы кортежа coordinates[0] = 5 # Правильно: создается новый кортеж с новыми значениями new_coordinates = (5, coordinates[1]) ```

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

Примитивные типы данных в Python

В Python термин «примитивные типы данных» обычно не используется, как, например, в языках программирования C или Java. Вместо этого, в Python используются базовые (или встроенные) типы данных, которые представляют основные категории информации, с которыми можно работать в программе. Вот основные базовые типы данных в Python и их роли в программировании:

Числовые типы данных (`int`, `float`)

  • `int` представляет целые числа без дробной части, а `float` представляет числа с плавающей точкой (с дробной частью).
  • Числовые типы данных используются для выполнения математических операций, хранения количественной информации и представления результатов вычислений.

Строковый тип данных (`str`)

  • `str` представляет последовательность символов, заключенных в кавычки.
  • Строковые типы данных используются для работы с текстовой информацией, отображения сообщений пользователю, обработки данных и многого другого.

Логический тип данных (`bool`)

Примитивные типы данных в Python

  • `bool` представляет логические значения `True` (истина) и `False` (ложь).
  • Логические типы данных используются для выполнения условных операций и принятия решений в программе.

Похожие материалы

Виртуальное окружение в разработке на языке Python

Моё мнение про Python

Операторы в Python: какие они бывают, как работают и где используются

Основные аспекты изучения Python

Какие основные встроенные типы данных представлены в Python?

В Python есть несколько встроенных типов данных, включая int (целые числа), float (вещественные числа), str (строки), list (списки), tuple (кортежи) и dict (словари).

Что такое изменяемые и неизменяемые типы данных?

Изменяемые типы данных (например, списки и словари) можно модифицировать после их создания, в то время как неизменяемые (например, строки и кортежи) — нет.

Как преобразовать строку в число и наоборот?

Для преобразования строки в число используйте функции int() или float(), а для преобразования числа в строку — str().

Что такое кортежи и как они отличаются от списков?

Кортежи — это неизменяемые последовательности. Основное отличие от списков заключается в том, что кортежи неизменяемы, а списки — изменяемы.

Как проверить тип переменной в Python?

Вы можете использовать встроенную функцию type().

Что такое динамическая типизация в 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) длинны (списки, кортежи и т.д.):

typedef struct _object  _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; > PyObject; 
  • _PyObject_HEAD_EXTRA — макрос, который определяет два поля _ob_next и _ob_prev — указатели на следующий и предыдущий объекты, соответственно. Будут ли эти поля включены в структуру PyObject или нет — зависит от флага Py_TRACE_REFS , который по умолчанию не установлен;
  • ob_refcnt — счетчик ссылок на объект, который увеличивается или уменьшается, при копировании или удалении указателя на объект; когда счетчик ссылок достигает нуля, то объект удаляется. Про подсчет ссылок и «сборку мусора» мы будем говорить в одной из следующих лекций;
  • ob_type — указатель на структуру _typeobject , которая задает тип объекта.

Структура PyVarObject включает одно дополнительное поле ob_size — количество элементов в объекте (например, для списка из пяти элементов ob_size будет равен 5):

typedef struct  PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ > PyVarObject; 

Связи между соответствующими структурами показаны на следующем рисунке:

Итак, если вы решили ввести свой тип, то он должен «наследоваться» от PyObject или PyVarObject с помощью макросов PyObject_HEAD и PyObject_VAR_HEAD :

#define PyObject_HEAD PyObject ob_base; . #define PyObject_VAR_HEAD PyVarObject ob_base; 
typedef struct _myobject  PyObject_HEAD . > PyMyObject; 

Таким образом, PyMyObject будет содержать все поля, которые есть в PyObject .

Следует помнить, что макрос PyObject_HEAD должнен идти первым в структуре. Это связано с «наследованием», о котором говорилось ранее. Как утверждается в object.h :

Objects are always accessed through pointers of the type PyObject * . The type PyObject is a structure that only contains the reference count and the type pointer. The actual memory allocated for an object contains other data that can only be accessed after casting the pointer to a pointer to a longer structure type. This longer type must start with the reference count and type fields; the macro PyObject_HEAD should be used for this (to accommodate for future changes). The implementation of a particular object type can cast the object pointer to the proper type and back.

и означает, что должна быть возможность приведения (casting) указателя на PyMyObject к указателю на PyObject , то есть:

PyObject *obj = (PyObject*)my_py_type_variable; 

Итак, PyObject и PyVarObject являются наиболее общими структурами для представления объектов в CPython, но пока мы не говорили о том как создаются новые объекты. В одной из последующих лекций мы вернемся к этому вопросу.

Целочисленный тип данных и числа с плавающей точкой

Без использования стандартной библиотеки языка нам доступны целые числа ( int ), вещественные числа ( float ) и комплексные числа ( complex ):

>>> year = 2021 >>> year 2021 >>> type(year) int >>> type(2021) int 

Процесс создания новой переменной называется name binding, то есть, связывание имени с некоторым объектом, в данном случае именем выступает year , а объектом целое число 2021 .

У каждого типа обычно есть «конструктор»:

>>> zero = int() >>> zero 0 >>> zero = float() >>> zero 0.0 

Основные арифметические операции:

>>> year + 1 2022 >>> year - 1 2020 >>> year * 12 24252 >>> year * 365.25 738170.25 >>> year / 100 20.21 

Взятие целой части и остатка от деления:

>>> year // 100 20 >>> year % 100 21 

Для записи очень больших или очень маленьких чисел удобно использовать экспоненциальную форму записи чисел. Сравните:

>>> 2.021 * 10**3 2021.0 >>> 2.021E3 2021.0 

И не стоит забывать про ошибки округления при работе с вещественными числами:

>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.9999999999999999 

Длинная арифметика в Python

Может ли произойти переполнение при работе с целыми числами в Python? Нет, если мы не говорим о таких пакетах как Numpy и Pandas, так как при работе с целыми числами в Python используется длинная арифметика 1 .

Структура PyLongObject отвечает за представление целых чисел:

struct _longobject  PyObject_VAR_HEAD digit ob_digit[1]; > PyLongObject; 

Если «раскрыть» макрос PyObject_VAR_HEAD , то стурктура будет выглядеть следующим образом:

struct _longobject  ssize_t ob_refcnt; struct _typeobject *ob_type; ssize_t ob_size; uint32_t ob_digit[1]; > PyLongObject; 

Связи между соответствующими структурами показаны на следующем рисунке:

Вы должны были заметить, что PyLongObject «наследуется» от PyVarObject , то есть является объектом переменной длины, и, таким образом, включает поле ob_size , которое в данном случае содержит размер массива ob_digit .

Все поля вам уже должны быть знакомы, PyLongObject добавляет лишь одно новое поле ob_digit — массив беззнаковых целых чисел по основанию 2^ . Давайте разберемся с назначением этого поля.

Представление произвольно больших целых чисел

Как хранить произвольно большое целое число? Одним из решений является представление целого числа в виде массива отдельных цифр. Для наиболее эффективного использования памяти мы можем конвертировать наше число из десятичной системы счисления в систему счисления по основанию 2^ , в таком случае каждый элемент представлен «цифрой» в диапазоне от 0 до 2^ — 1 . В зависимости от платформы Python использует или 32-битные беззнаковые массивы с 30-битными цифрами или 16-битные беззнаковые массивы с 15-битными цифрами. Такой подход представления больших целых чисел связан с дополнительными ограничениями, которые и не позволяют использовать все биты. Поле ob_digit структуры показанной выше, содержит такие массивы цифр.

Для избежания лишних вычислений в CPython есть эффективный способ представления целых чисел в диапазоне от -2^ до 2^ . Такие целые числа хранятся как массивы с одним элементом, то есть, состоящие из одной цифры.

Также следует отметить, что в отличие от классического представления знака числа (т.е. использования знакового бита), знак целого числа хранится в поле ob_size , которое также содержит размер массива ob_digit . Например, если мы хотим изменить знак целого с размером ob_size=2 (две цифры), то ob_size станет равным -2 .

Комментарий из исходных текстов по представлению целых чисел:

/* Long integer representation. The absolute value of a number is equal to SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) Negative numbers are represented with ob_size < 0; zero is represented by ob_size == 0. In a normalized number, ob_digit[abs(ob_size)-1] (the most significant digit) is never zero. Also, in all cases, for all valid i, 0  The allocation function takes care of allocating extra memory so that ob_digit[0] . ob_digit[abs(ob_size)-1] are actually available. CAUTION: Generic code manipulating subtypes of PyVarObject has to aware that integers abuse ob_size's sign bit. */ 

Давайте рассмотрим конкретный пример преобразования длинного целого в массив и обратно. Пусть у нас имеется следующее число: 123456789101112131415 . Переведем его в систему счисления по основанию 2^ , путем последовательного деления и записи остатка от деления:

Конвертировать число обратно также достаточно просто:

(437976919 ∗ 2^<30 ∗ 0>) + (87719511 ∗ 2^<30 ∗ 1>) + (107 ∗ 2^<30 ∗ 2>) = 123456789101112131415

Преобразования длинного целого в массив

Ниже приведен упрощенный вариант алгоритма представления произвольно больших чисел:

SHIFT = 30 # Число бит под каждую «цифру» MASK = (2 ** SHIFT) def split_number(bignum): t = abs(bignum) num_list = [] while t != 0: # Взятие остатка от деления small_int = t % MASK # Побитовый аналог: (t & (MASK-1)) num_list.append(small_int) # Взятие целой части от деления t = t // MASK # Побитовый аналог: t >>= SHIFT return num_list def restore_number(num_list): bignum = 0 for i, n in enumerate(num_list): bignum += n * (2 ** (SHIFT * i)) return bignum 
>>> bignum = 123456789101112131415 >>> num_list = split_number(bignum) >>> num_list [437976919, 87719511, 107] >>> bignum == restore_number(num_list) True 

Если мы хотим убедиться, что нигде не ошиблись, то можем посмотреть на внутреннее представление целого числа с помощью модуля ctypes, который позволяет взаимодействовать с Си-кодом из Python:

import ctypes class PyLongObject(ctypes.Structure): _fields_ = [("ob_refcnt", ctypes.c_ssize_t), ("ob_type", ctypes.c_void_p), ("ob_size", ctypes.c_ssize_t), ("ob_digit", ctypes.c_uint * 3)] 
>>> bignum = 123456789101112131415 >>> for i,d in enumerate(PyLongObject.from_address(id(bignum)).ob_digit): . print(f"ob_digit[] = ") ob_digit[0] = 437976919 ob_digit[1] = 87719511 ob_digit[2] = 107 >>> print("ob_size:", PyLongObject.from_address(id(bignum)).ob_size) ob_size: 3 

Оптимизации

Небольшие целые числа в диапазоне от -5 до 256 преаллоцируются в процессе инициализации интерпретатора. Так как целые числа являются неизменяемыми, то мы можем воспринимать их как синглтоны. Каждый раз, когда нам необходимо создать небольшое целое число (например, как результат некоторой арифметической операции), то вместо создания нового объекта, Python просто возвращает указатель на уже преаллоцированный объект. Это позволяет сократить количество потребляемой памяти и время затрачиваемое на вычисления при работе с небольшими целыми числами.

Давайте рассмотрим простой пример:

>>> a = 2 >>> id(a) 94220163919104 >>> a = a + 1 >>> id(a) 94220163919136 >>> b = 2 >>> id(b) 94220163919104 

Следует иметь ввиду, что структура PyLongObject занимает не менее 28 байт для каждого целого числа, то есть в три раза больше чем требуется под 64-битное целое в языке C.

>>> import sys >>> sys.getsizeof(1) 28 

Из чего складывается такой размер? Указатель на структуру _typeobject занимает восемь байт, также по восемь байт занимают поля ob_refcnt и ob_size , что уже в сумме дает нам 24 байта. Каждый элемент массива ob_digit это еще четыре байта. Итого для небольших целых чисел требуется 28 байт. Но есть одно исключение — ноль:

>>> import sys >>> sys.getsizeof(0) 24 

Выполнение арифметических операций

Базовые арифметические операции выполняются аналогично тому, как мы это делали когда-то в школе, с одним исключением: каждый элемент массива считается «цифрой».

Давайте рассмотрим вариант алгоритма сложения с переносом:

def add_bignum(a, b): z = [] if len(a)  len(b): # Убедимся, что в «a» наибольшее из двух значений a, b = b, a carry = 0 for i in range(0, len(b)): carry += a[i] + b[i] z.append(carry % MASK) carry = carry // MASK for i in range(i + 1, len(a)): carry += a[i] z.append(carry % MASK) carry = carry // MASK z.append(carry) # Удалим завершающие нули i = len(z) while i > 0 and z[i-1] == 0: i -= 1 z = z[0:i] return z 
>>> a = 8223372036854775807 >>> b = 100037203685477 >>> restore_number(add_bignum(split_number(a), split_number(b))) == a + b True 

Замечание про Numpy и Pandas

В тех случаях, когда мы пользуемся библиотеками numpy/scipy/pandas и т.д., может произойти переполнение при работе с целыми числами, так как структуры, лежащие в основе этих библиотек, для более эффективного использования памяти, полагаются на соответствующие С-типы ограниченной точности 2 :

>>> import numpy as np >>> ar = np.array([2**63 - 1, 2**63 - 1]) >>> ar array([9223372036854775807, 9223372036854775807]) >>> ar.dtype dtype('int64') 

Элементами ndarray являются 64-битные знаковые целые, таким обрзаом, 2^-1 наибольшее положительное значение, которое мы можем хранить в ndarray . Добавление 1 приведет к переполнению (overflow):

>>> ar + 1 array([-9223372036854775808, -9223372036854775808]) >>> np.sum(ar) -2 

При вычислении среднего элементы массива сначала приводятся к типу float и переполнения не возникает:

>>> np.mean(ar) 9.2233720368547758e+18 

Числа с плавающей точкой и стандарт IEEE-754

Вещественные числа в CPython представлены структурой PyFloatObject :

typedef struct  PyObject_HEAD double ob_fval; > PyFloatObject; 

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

static PyObject * float_add(PyObject *v, PyObject *w)  double a,b; CONVERT_TO_DOUBLE(v, a); CONVERT_TO_DOUBLE(w, b); PyFPE_START_PROTECT("add", return 0) a = a + b; PyFPE_END_PROTECT(a) return PyFloat_FromDouble(a); > 

Следует помнить, что все вычисления в вещественных числах делаются компьютером с некоторой ограниченной точностью (см. стандарт IEEE-754), поэтому зачастую вместо «честных» ответов получаются приближенные (к этому надо быть готовым), например:

>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.9999999999999999 

Если вы не понимаете почему мы не получили единицу, то попробуйте перевести число 0.1 в двоичную систему счисления:

0.1 = \frac<1> = 0*2^ + 0*2^ + 0*2^ + 1*2^ + 1*2^ + . = 00011(0011)

В некоторых случаях на помощь может придти модуль fmath :

>>> from math import fsum >>> sum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) 0.9999999999999999 >>> fsum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) 1.0 

Булевый тип

>>> to_be = True >>> to_be or not to_be True 
>>> is_leap = (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0) >>> is_leap False 
>>> True or abrakadabra_or_lazy_evaluation True 
>>> isinstance(True, bool) and isinstance(True, int) True 

Строки

Строки в Python версии 3 представляют собой последовательность символов Юникод (code point’ов). Если вы никогда не слышали про Юникод и кодировки или плохо представляете, что это такое, то советую прочитать исчерпывающую статью David C. Zentgraf из серии «Что каждый программист должен знать о. ».

Новую строку можно создать с помощью одинарных или двойных кавычек (можно использовать и тройные кавычки, но чаще они используются для документирования функций, классов, модулей):

>>> first_name = 'Dmitrii' >>> last_name = 'Sorokin' >>> email = 'Dementiy@yandex.ru' 

Для строк определена операция сложения (конкантенации):

>>> full_name = first_name + ' ' + last_name >>> full_name 'Dmitrii Sorokin' 

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

>>> full_name = ' '.join([first_name, last_name]) >>> full_name 'Dmitrii Sorokin' 

Здесь мы первый раз сталкиваемся с вызовом метода у объекта. Каждый объект предоставляет интерфейс (методы) взаимодействия с ним, а также хранит внутреннее состояние посредством переменных. Обращение к методам и переменным объекта (атрибутам) происходит через точку, как в примере выше с методом join() .

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

>>> email.split('@') ['Dementiy', 'yandex.ru'] >>> username, domain = email.split('@') >>> username 'Dementiy' >>> domain 'yandex.ru' 

Заканчивается ли строка данной подстрокой:

>>> email.endswith('yandex.ru') True 

У строк (как и у большинства контейнеров) можно получить длину (число элементов в контейнере):

>>> len(full_name) # --> full_name.__len__() 15 

Можно обращаться к отдельным элементам строки, которые представляют собой строку из одного символа:

>>> first_name[0] 'D' >>> first_name[-1] 'i' 

Строки являются неизменяемыми, то есть мы не можем изменить отдельный элемент строки:

>>> first_name[-1] = 'y' . TypeError: 'str' object does not support item assignment 

И наконец мы можем брать подмножество (срез) элементов строки:

>>> email[:email.index('@')] 'Dementiy' 

Мы рассмотрели лишь небольшую часть операций, которые можно выполнять над строками. Полный список операций вы всегда можете найти в документации.

Представление строк

Как уже было сказано, строки в Python являются Юникод строками. Для внтуреннего представления строк в Python, начиная с версии 3.3 (см. PEP-393), используются кодировки Latin-1 (1 байт на символ), UCS-2 (2 байта на символ) и UCS-4 (4 байта на символ). Упрощенно процесс определения кодировки следующий: когда необходимо создать строковый объект (тексты программ обычно в кодировке UTF-8), Python находит самый старший кодовый знак (code point) в строке и выбирает кодироку, в которой кодовый знак может быть представлен «как есть».

Строки представлены не одной структурой, а «иерархией» из трех структур, не считая PyObject . Мы рассмотрим одну структуру — PyASCIIObject , которая содержит большую часть информации о строке, например, какая кодировка используется для хранения строки, длину строки (число кодовых знаков), состоит ли строка только из ASCII-символов, интернирована строка или нет и т.д.

Опишем структуру PyASCIIObject с помощью модуля ctypes :

import ctypes class PyASCIIObject(ctypes.Structure): _fields_ = [("ob_refcnt", ctypes.c_ssize_t), ("ob_type", ctypes.py_object), ("length", ctypes.c_ssize_t), ("hash", ctypes.c_ssize_t), ("interned", ctypes.c_uint, 2), ("kind", ctypes.c_uint, 3), ("compact", ctypes.c_uint, 1), ("ascii", ctypes.c_uint, 1), ("ready", ctypes.c_uint, 1), ('wstr', ctypes.c_wchar_p)] def get_string_kind(string): return PyASCIIObject.from_address(id(string)).kind 

Создадим несколько строковых объектов:

>>> greet = 'Hello, world' >>> greet 'Hello, world' >>> len(greet) 12 >>> sys.getsizeof(greet) 61 >>> get_string_kind(greet) 1 >>> greet = 'Hello, 世界' >>> greet 'Hello, 世界' >>> len(greet) 9 >>> sys.getsizeof(greet) 92 >>> get_string_kind(greet) 2 >>> greet = 'Hello, \U0001F30D' >>> greet 'Hello, ��' >>> len(greet) 8 >>> sys.getsizeof(greet) 108 >>> get_string_kind(greet) 4 

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

Почему для внутреннего представления строк не используется UTF-8? Кодировка UTF-8 подразумевает, что может использоваться варьируемое число байт (от одного до четырех) для кодирования одного символа. UTF-8 является оптимальной кодировкой с точки зрения хранения строк (то есть кодировка эффективна по памяти), но при обращении к отдельным элементам строки (при индексировании), необходимо пройтись по всем символам строки пока мы не дойдем до нужного символа. При фиксированном размере такой проблемы не возникает, для перехода к нужной позиции символа достаточно индекс умножить на размер кодового знака (1, 2 или 4 в зависимости от используемой кодировки). Тем не менее есть мнение, что индексация это не проблема.

Интернирование строк

Дополнительно про интернирование можно почитать тут и тут.

Для экономии памяти в Python реализовано интернирование строк (string interning). Давайте рассмотрим такой пример, пусть у нас есть два строковых объекта с одинаковым содержимым:

>>> s1 = "foo!" >>> s2 = "foo!" >>> s1 is s2 False 

Хотя содержимое строк совпадает это два разных объекта. С другой стороны:

>>> s1 = "a" >>> s2 = "a" >>> s1 is s2 True 

получим, что адреса s1 и s2 совпадают. Все строки длиной 0 или 1 интернированы, кроме того интернируются все строковые литералы, состоящие из символов латинского алфавита, цифр или нижнего подчеркивания, также интернируются имена переменных, функций, классов и т.д.

interned = None def intern(string): global interned if string is None or not type(string) is str: raise TypeError if interned is None: interned = <> t = interned.get(string) if t is not None: return t interned[string] = string return string 

Если мы хотим интернировать строку, то следует воспользоваться функцией intern из модуля sys :

>>> import sys >>> s1 = sys.intern("foo!") >>> s2 = sys.intern("foo!") >>> s1 is s2 True 

Использование интернирования строк гарантирует, что не будет создано двух одинаковых строковых объектов. Когда вы создаете второй объект с тем же значением, что и у существующего объекта, то вы получаете ссылку на уже существующий объект. Таким образом, интернирование строк позволяет экономить память и повышает скорость сравнения строк, путем сравнения их адресов (хешей), а не содержимого.

Кортежи

Последний неизменяемый тип, который мы рассмотрим в этой лекции это кортежи, которые фактически являются статическими массивами, то есть, имеют фиксированный размер, и представлены структурой PyTupleObject :

typedef struct  PyObject_VAR_HEAD /* ob_item contains space for 'ob_size' elements. Items must normally not be NULL, except during construction when the tuple is not yet visible outside the function that builds it. */ PyObject *ob_item[1]; > PyTupleObject; 

Рассмотрим простой пример создания кортежа из трех элементов:

>>> point = (1.0, 2.0, 3.0) >>> point (1.0, 2.0, 3.0) 

Как уже было отмечено, важной особенностью кортежей является то, что это неизменяемая структура:

>>> point[0] = 4 . TypeError: 'tuple' object does not support item assignment 

Когда мы говорим, что кортежи неизменяемые, то имеем ввиду, что мы не можем заменить один элемент кортежа на другой, но сам объект изменить мы можем:

>>> t = (1, [2]) >>> t[1].append(3) >>> t (1, [2, 3]) 
  1. Значительная часть материала про представление целых чисел взята из статьи Артема Голубина: Python Integer Implementation. ↩
  2. Can integer operations overflow in Python?. ↩

Изменяемые и неизменяемые типы данных

В Python все типы данных делятся на изменяемые и неизменяемые (англ. mutable и immutable). В этой статье мы рассмотрим, какие типы к какому виду относятся и в чем различие между ними.

Оглавление

  • Что такое изменяемые и неизменяемые объекты в Python
    • Определение изменяемости
    • Определение неизменяемости
    • Список изменяемых и неизменяемых объектов
    • Где используются изменяемые и неизменяемые объекты?
    • Нетранзитивный характер неизменяемости
    • Неизменяемость кортежа
    1. В чем разница между изменяемыми и неизменяемыми объектами в Python?
    2. Что такое изменяемые и неизменяемые типы данных в Python?
    3. Являются ли списки изменяемыми типами данных в Python?
    4. Почему кортежи относятся к неизменяемым типам данных в Python?
    5. Являются ли множества изменяемым типом данных?
    6. Являются ли строки изменяемым типом данных?

    Что такое изменяемые и неизменяемые объекты в Python

    Самое простое определение: объект, внутреннее состояние которого может быть изменено, является изменяемым. И напротив, понятие «неизменяемый объект» подразумевает, что никаких изменений в объекте после его создания быть не может.

    Определение изменяемости

    Изменяемость сама по себе подразумевает, что что-либо может быть изменено. В Python это означает способность объектов менять свои значения. Как правило, это объекты, в которых хранится набор данных.

    Определение неизменяемости

    Неизменяемость, в свою очередь, подразумевает, что никакие изменения с течением времени невозможны. Соответственно, если значение объекта не может быть изменено, он называется неизменяемым. После создания таких объектов их значения являются постоянными.

    Список изменяемых и неизменяемых объектов

    Вот встроенные типы данных Python, объекты которых являются изменяемыми:

    • Списки
    • Множества
    • Словари
    • Определенные пользователем классы (это целиком и полностью зависит от того, как именно пользователь определяет свой класс)

    А вот встроенные типы данных Python, объекты которых являются неизменяемыми:

    • Числа
    • Строки
    • Кортежи
    • Замороженные множества (Frozen Sets)
    • Определенные пользователем классы (это целиком и полностью зависит от того, как именно пользователь определяет свой класс)

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

    Объекты в Python

    В Python всё является объектом. И у каждого объекта есть следующие три атрибута:

    • идентичность: адрес в памяти, на который ссылается данный объект
    • тип: тип создаваемого объекта. Например, целое число, список, строка и т. д.
    • значение: значение, которое хранится в данном объекте. Например, список [1, 2, 3] будет содержать числа 1, 2 и 3.

    После создания объекта его идентификатор и тип изменить нельзя, а вот значение в изменяемых объектах может быть изменено.

    Изменяемые объекты в Python

    Вместо того, чтобы углубляться в теоретические аспекты изменяемых и неизменяемых объектов в Python, давайте лучше рассмотрим простой код. Разберем приведенный ниже код шаг за шагом:

    # Создание списка, содержащего названия индийских городов cities = ['Delhi', 'Mumbai', 'Kolkata'] # Вывод в консоль элементов из списка городов, разделенных запятой и пробелом for city in cities: print(city, end=', ') # Результат [1]: Delhi, Mumbai, Kolkata # Вывод в консоль местоположения созданного объекта в адрес памяти в шестнадцатеричном формате print(hex(id(cities))) # Результат [2]: 0x1691d7de8c8 # Добавление нового города в список городов cities.append('Chennai') # Вывод в консоль элементов из списка городов, разделенных запятой и пробелом for city in cities: print(city, end=', ') # Результат [3]: Delhi, Mumbai, Kolkata, Chennai # Вывод в консоль местоположения созданного объекта в адрес памяти в шестнадцатеричном формате print(hex(id(cities))) # Результат [4]: 0x1691d7de8c8

    Приведенный выше пример показывает, что мы смогли изменить внутреннее состояние объекта cities , добавив к нему еще один город Chennai , однако адрес памяти объекта не изменился. Это подтверждает тот факт, что мы изменили уже существующий объект, а не создали новый. Следовательно, можно заключить, что объект типа список (list), ссылка на который заключена в переменной cities , является изменяемым.

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

    Вот простейшее определение неизменяемого объекта: объект, внутреннее состояние которого изменить нельзя, является неизменяемым.

    И еще, идентифицировать неизменяемые объекты в Python можно, если сконцентрироваться на сообщениях об ошибках, которые позволяет видеть ваша среда программирования (IDE). Например, рассмотрим приведенный ниже код и связанное с ним сообщение об ошибке при попытке изменить значение элемента кортежа с индексом 0.

    # Создание кортежа с именем переменной «foo» foo = (1, 2) # Изменение значения foo[0] с 1 на 3 foo[0] = 3 # Результат: # TypeError: 'tuple' object does not support item assignment

    Неизменяемые объекты в Python

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

    # Создание кортежа, содержащего английские названия дней недели weekdays = 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' # Вывод в консоль элементов кортежа weekdays print(weekdays) # Результат [1]: ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday') # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(weekdays))) # Результат [2]: 0x1691cc35090 # Кортежи являются неизменяемыми, поэтому вы не можете добавлять новые элементы. Поэтому попробуем добавить новый воображаемый день в кортеж weekdays, используя слияние кортежей, при помощи оператора += weekdays += 'Pythonday', # Вывод в консоль элементов кортежа weekdays print(weekdays) # Результат [3]: ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Pythonday') # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(weekdays))) #Результат [4]: 0x1691cc8ad68

    Как видите, при добавлении нового элемента в кортеж мы смогли воспользоваться тем же именем переменной, которое ссылалось на старый кортеж, состоящий из семи элементов. Однако идентификатор, который показывает расположение в памяти объекта кортежа, не остался неизменным. Нам не удалось изменить внутреннее состояние объекта weekdays .

    Программный менеджер Python создал в памяти новый объект, и переменная weekdays стала ссылаться на него, то есть на новый объект с восемью элементами. Следовательно, мы можем заключить, что объект типа кортеж, на который ссылается переменная weekdays, является неизменяемым.

    Где используются изменяемые и неизменяемые объекты?

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

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

    Нетранзитивный характер неизменяемости

    Итак, мы разобрались, что собой представляют изменяемые и неизменяемые объекты в Python. Давайте теперь рассмотрим комбинацию этих двух типов данных. Рассмотрим, что будет происходить, если взять неизменный тип данных, состоящий из элементов, которые являются изменяемыми объектами. Или наоборот.

    Как обычно, разберем это на конкретном примере кода:

    # Создаем кортеж (неизменяемый объект), который содержит в себе 2 списка (изменяемых объекта) в качестве элементов # Элементы списков содержат имя, возраст и пол person = (['Ayaan', 5, 'Male'], ['Aaradhya', 8, 'Female']) # Выводим кортеж в консоль print(person) # Результат [1]: (['Ayaan', 5, 'Male'], ['Aaradhya', 8, 'Female']) # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(person))) # Результат [2]: 0x1691ef47f88 # Изменяем возраст для 1-го элемента кортежа. Выбираем 1-й элемент кортежа с использованием индексации [0], а затем 2-ой элемента списка с использованием индексации [1] и присваиваем ему нового значения для возраста равное 4 person[0][1] = 4 # Вывод в консоль обновленного кортежа print(person) # Результат [3]: (['Ayaan', 4, 'Male'], ['Aaradhya', 8, 'Female']) # Вывод местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(person))) # Результат [4]: 0x1691ef47f88

    В этом коде есть неизменяемый объект типа кортеж, на который ссылается переменная person . Однако в качестве элементов у него есть два списка, и мы можем изменить состояние этих списков, так как списки изменяемы. Итак, здесь мы не изменили ссылку на объект внутри кортежа, но объект, на который указывает эта ссылка, был видоизменен.

    Теперь давайте посмотрим, что будет, если есть изменяемый объект, внутри которого в качестве элементов присутствуют неизменяемые объекты.

    # Создаем список (изменяемый объект), который содержит кортежи (неизменяемые объекты) в качестве элементов list1 = [(1, 2, 3), (4, 5, 6)] # Выводим список в консоль print(list1) # Результат [1]: [(1, 2, 3), (4, 5, 6)] # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(list1))) #Результат [2]: 0x1691d5b13c8 # Меняем первый элемент списка на другой кортеж list1[0] = (7, 8, 9) # Выводим список в консоль print(list1) # Результат [3]: [(7, 8, 9), (4, 5, 6)] # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(list1))) # Результат [4]: 0x1691d5b13c8

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

    Перед тем как мы закончим изучение неизменяемых объектов, рассмотрим одно исключение из общих правил. Результаты будут несколько неожиданными:

    # Создание объекта целочисленного типа со значением 10 и именем переменной ‘x’, которая ссылается на этот объект x = 10 # вывод в консоль значения ‘x’ print(x) # Результат [1]: 10 # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(x))) # Результат [2]: 0x538fb560 # Cоздание объекта целочисленного типа со значением 10 и именем переменной ‘y’, которая ссылается на этот объект y = 10 # Вывод в консоль значения ‘y’ print(y) # Результат [3]: 10 # Вывод в консоль местоположения созданного объекта в памяти в шестнадцатеричном формате print(hex(id(y))) # Результат [4]: 0x538fb560

    Согласно нашему пониманию, адрес объектов, находящихся в переменных x и y , должен быть разным, поскольку значение 10 — объект класса Integer, который является неизменяемым. Однако, как видно из кода выше, адрес у них один и тот же. Это не совсем то, что мы ожидали. Следовательно, существуют исключения из правил.

    Неизменяемость кортежа

    Кортежи относятся к неизменяемому типу данных и, следовательно, не могут быть изменены после их создания в Python. Это потому, что они поддерживают те же последовательности операций, что и строки. А мы все знаем, что строки являются неизменяемыми объектами. С помощью оператора индекса можно выбрать элемент кортежа точно так же, как и в строке. Таким образом, кортежи неизменяемы.

    Исключения в неизменяемости

    Как и везде, в языке Python также существуют исключения из правил. В том числе они касаются и понятия неизменяемости. Некоторые неизменяемые объекты в действительности могут быть изменены. Это несколько сбивает с толку. Чтобы разобраться, давайте рассмотрим простой пример.

    Допустим, у нас есть кортеж под именем tup .

    tup = ('GreatLearning', [4,3,1,2])

    Мы видим, что в этом кортеже есть элементы разных типов данных. Первым элементом здесь является строка, которая, как хорошо известно, неизменяема по своей природе. А второй элемент — список, который является изменяемым типом данных, что также общеизвестно.

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

    FAQ

    1. В чем разница между изменяемыми и неизменяемыми объектами в Python?

    Изменяемый объект Неизменяемый объект
    Состояние объекта может быть изменено после его создания. Состояние объекта не может быть изменено после его создания.
    Объект не потокобезопасен. Объект потокобезопасен.
    Изменяемые классы не являются окончательными. При создании неизменяемого объекта важно сделать его окончательным, например, при помощи модификатора Final .

    2. Что такое изменяемые и неизменяемые типы данных в Python?

    • Изменяемыми типами данных в Python являются: списки, словари, множества и определенные пользователем классы
    • Неизменяемыми типами данных в Python являются: числа (int, float, decimal), логический тип данных (bool), строки (string), кортежи (tuple), range

    3. Являются ли списки изменяемыми типами данных в Python?

    Списки в Python являются изменяемыми типами данных, так как элементы списка могут быть изменены. Отдельные элементы списка могут быть заменены, а порядок элементов может быть изменен даже после создания списка.

    Примеры, связанные со списками, разбирались ранее в данной статье.

    4. Почему кортежи относятся к неизменяемым типам данных в Python?

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

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

    5. Являются ли множества изменяемым типом данных?

    Множество (set) — это итерируемая неупорядоченная коллекция, которую можно использовать для выполнения математических операций (таких как объединение, пересечение, разность и т. д.). Каждый элемент в этой коллекции уникален и неизменен, т. е. не должно быть повторяющихся значений, и значения нельзя изменить. Однако мы можем добавлять или удалять элементы из множества, так как само множество является изменяемым объектом.

    6. Являются ли строки изменяемым типом данных?

    Строки в Python являются неизменяемыми объектами. Это означает, что их значение не может быть обновлено.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *