Перейти к основному содержимому

Типы данных в Python

Разработчику Архитектору

Дальше: Работа с типами — преобразования и операции по типам · Переменные · Справочник Python

Обобщения (typing): Обобщения и обобщённое программирование — теория; ниже и в typing — синтаксис Python.


Типизация в Python

Основы типизации в Python

Теория типов и осей "статика/динамика", "сильная/слабая", типобезопасность — в статьях Типы данных и Типизация.

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

Python описывают как динамически и сильно типизированный — тип принадлежит объекту, проверка идёт в runtime, неявные преобразования между несовместимыми типами не выполняют ("5" + 2TypeError). Аннотации (age: int = 25) необязательны; статический анализ дают mypy, pyright и IDE — это постепенная типизация, не смена модели интерпретатора.

Термин "неявная типизация" в русскоязычных текстах часто путают со слабой — в Python это разные вещи.


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

Тип принадлежит объекту, а не "ящику" переменной. Имя в коде — это ссылка на объект; тип проверяется во время выполнения.

x = 42 # x ссылается на int(42)
x = "привет" # x теперь ссылается на str — другой объект
x = [1, 2, 3] # x ссылается на list

Разбор:

  • Оператор = в Python связывает имя с объектом, а не "меняет тип переменной".
  • После каждого присваивания x указывает на новый объект в памяти.
  • Типом управляет сам объект (int, str, list), поэтому проверка типов происходит в runtime. Одно имя может указывать на объекты разных типов в разные моменты времени. Аннотации (x: int = 1) необязательны и не меняют поведение интерпретатора по умолчанию. Подробнее о модели имён — в главе про переменные.

Типы без объявления в коде

При присваивании не нужно писать int age = 30 — интерпретатор создаёт объект нужного типа из литерала или выражения:

age = 30 # int
name = "Алиса" # str

Это не "слабая типизация": Python не склеивает произвольные типы в арифметике и конкатенации без явного преобразования.


Сильная типизация

Между несовместимыми типами нет "магического" приведения:

"возраст: " + 25 # TypeError

Разбор:

  • Оператор + для str ожидает справа строку.
  • Попытка сложить str и int вызывает TypeError, так как автоматического склеивания разных типов нет.
  • Для корректной конкатенации нужно явное преобразование через str(25).

В языках со слабой типизацией (например, JavaScript) такое выражение часто даёт "возраст: 25". В Python нужно явно:

"возраст: " + str(25)

Узкие допустимые преобразования в выражениях всё же есть — это фиксирует правила:

True + 1 # 2 — bool наследует int
3 + 4.5 # 7.5 — int с float даёт float
bool("text") # True — явное преобразование через bool()

Play ITЗагрузка интерактивного демо…


Play ITЗагрузка интерактивного демо…


Типы данных в Python

Категории типов данных

Все типы данных в Python можно разделить на две большие категории:

  • Изменяемые (mutable, мутабельные) - объект можно изменить после создания (как раз list, dict, set);
  • Неизменяемые (immutable, иммутабельные) - значение фиксируется при создании (int, str, tuple, frozenset).

Неизменяемые объекты безопасны при передаче в функции — их нельзя случайно изменить. Они могут использоваться как ключи в словарях (потому что хэшируемы). При "изменении" неизменяемого объекта создаётся новый объект:

s = "hello"
s = s + " world" # Это НОВАЯ строка, старая удаляется сборщиком мусора

Изменяемые объекты позволяют добавлять, удалять или изменять элементы "на месте":

my_list = [1, 2]
my_list.append(3) # my_list изменился, id остаётся тем же

Python — динамически и сильно типизированный язык: тип известен у объекта в runtime, а int + str без преобразования вызовет ошибку.

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

Неизменяемые (immutable) объекты нельзя изменить после создания — числа (int, float, complex), строки (str), кортежи (tuple), frozenset, bool, None. Слово "атомарный" в других контекстах означает неделимость операции в потоках — не путайте с immutability. При "изменении" строки или числа создаётся новый объект, имя переназначается на него.

a = 42
b = a # b ссылается на тот же объект
a = a + 1 # Создаётся новый int(43), a указывает на него
print(b) # 42 — значение b не изменилось

== сравнивает значения; is — один ли это и тот же объект в памяти (id совпадает). Для проверки на отсутствие значения используйте x is None, а не x == None.

x = "hello"
y = "hello"
print(x == y) # True — одинаковое содержимое
print(x is y) # может быть True для коротких строк (интернирование)
print(id(x), id(y))

Изменяемые (mutable) объекты могут менять содержимое, сохраняя тот же id: К ним относятся:

  • Списки (list)
  • Словари (dict)
  • Множества (set)
  • Экземпляры пользовательских классов

Пример:

lst1 = [1, 2, 3]
lst2 = lst1 # lst2 ссылается на тот же объект
lst2.append(4)
print(lst1) # [1, 2, 3, 4] — изменился и lst1!

Разбор:

  • lst2 = lst1 копирует ссылку, обе переменные смотрят на один list.
  • append(4) меняет объект "на месте", поэтому изменение видно через оба имени.
  • Для независимых коллекций нужен copy() или deepcopy() в случае вложенных структур. Чтобы избежать неожиданного поведения, нужно создавать копии:
lst2 = lst1.copy() # Поверхностная копия
# или
lst2 = lst1[:] # Для списков
# или

import copy

lst2 = copy.deepcopy(lst1) # Глубокая копия (для вложенных структур)

copy() и срез [:]поверхностная копия: вложенные списки внутри dict/list останутся общими. Даже неизменяемые объекты живут в куче как полноценные объекты; разница в том, что append меняет список на месте, а a = a + 1 для int создаёт новый int.

А теперь рассмотрим все встроенные типы подробнее.


Число

Число (int, float, complex) поддерживает целые (int), вещественные (float) и комплексные числа (complex). int не ограничен по размеру (в отличие от многих других языков).

<переменная> = <целое_число>
<переменная> = <вещественное_число>
<переменная> = <вещественное_число>e<степень>
<переменная> = <вещественное_число>j
<переменная> = <целое_число> + <вещественное_число>j

Простой пример:

age = 30

Сложный пример:


import math

def calculate_compound_interest(principal, rate, times_per_year, years):
return principal * (1 + rate / times_per_year) ** (times_per_year * years)

final_amount = round(calculate_compound_interest(1000, 0.05, 12, 10), 2)

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

int — целые числа. Могут быть любого размера (ограничены только памятью). Поддерживают отрицательные значения.

x = 100
big_number = 10**100 # Очень большое число — работает!

float — вещественные числа. Представлены в формате с плавающей точкой (IEEE 754). Имеют ограниченную точность (~15–17 знаков).

pi = 3.1415926535
scientific = 6.02e23 # 6.02 × 10²³

Из-за особенностей представления 0.1 + 0.2 != 0.3 — классическая проблема всех float. Для финансовых вычислений лучше использовать модуль decimal.

complex — комплексные числа. Формат: a + bj, где j — мнимая единица.

z = 3 + 4j
print(z.real) # 3.0
print(z.imag) # 4.0

Используются в научных расчётах, сигнал-обработке и математике.

Примеры:

  • count = 42
  • price = 199.99
  • avogadro = 6.02e23
  • imaginary = 5j
  • complex_num = 3 + 4j

Булево

Булево (bool) это подтип int, где True == 1, False == 0. Возникает как результат сравнений, логических выражений.

<переменная> = True
<переменная> = False
<переменная> = <выражение_сравнения>
<переменная> = <логическое_выражение>

Простой пример:

is_active = True

Сложный пример:

is_eligible = (
user['age'] >= 18 and
user['verified'] and
any(role in user['roles'] for role in ['admin', 'moderator']) and
not user.get('blocked', False)
)

Здесь условие включает проверку возраста, флага верификации, наличие одной из ролей через генератор выражения (any) и безопасное извлечение поля с помощью get. Результат — булево значение, вычисленное на основе структурированных данных.

Два значения: True и False. Является подклассом int: True == 1, False == 0.

is_ready = True
if is_ready:
print("Готов!")

Любое значение можно привести к булеву через bool():

bool(0) # False
bool("") # False
bool([1, 2]) # True

Полезно в условиях и проверках.

Примеры:

  • is_valid = True
  • has_permission = user.role == "admin"
  • ready = (x > 0) and (y < 100)

Строка

Строка (str) это неизменяемая последовательность символов Unicode. Поддерживает f-строки, форматирование, методы, индексацию, срезы.

Строки неизменяемы — любое "изменение" создаёт новую строку. Разворот за одну запись — срез s[::-1] (однострочные приёмы). Для списков тот же срез даёт новый list, а разворот на месте — lst.reverse() (подробнее). Частые методы (upper, find, replace, split и проверки is…) — в Работа с типами — методы строк.

<переменная> = "<текст>"
<переменная> = '<текст>'
<переменная> = """<многострочный текст>"""
<переменная> = f"<шаблон {выражение}>"
<переменная> = "<шаблон {}>".format(<значение>)

Простой пример:

name = "Тимур"

Сложный пример:

from datetime import datetime

greeting = f"""
Добро пожаловать, {user.get('name', 'Гость').title()}!

Сегодня: {datetime.now().strftime('%d.%m.%Y')}
Время: {datetime.now().strftime('%H:%M')}

Ваш баланс: {user.get('balance', 0):,.2f} ₽.
""".strip()

Здесь F-строка с вложенными вызовами методов, форматированием даты, заглавными буквами имени и денежного формата (:,.2f). Использовано безопасное извлечение значений и удаление лишних пробелов.

F-строки (форматированные строковые литералы) появились в Python 3.6 — самый удобный способ форматирования строк.

name = "Мария"
age = 28
greeting = f"Привет, {name}! Тебе {age} лет."

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

result = f"Квадрат 5: {5 ** 2}, длина имени: {len(name)}"

F-строки поддерживают форматирование:

price = 1234.5678
formatted = f"Цена: {price:,.2f} ₽" # "Цена: 1,234.57 ₽"
date = f"Дата: {datetime.now():%d.%m.%Y}"

Примеры:

  • name = "Алиса"
  • message = 'Привет, мир!'
  • doc = """Это многострочная строка."""
  • greeting = f"Здравствуйте, {user.name}!"
  • report = "Баланс: {:.2f}".format(balance)

None

None — специальное значение, обозначающее отсутствие значения.

<переменная> = None

Примеры:

  • result = None
  • cache = None
  • callback = None
result = None

У него есть собственный тип: NoneType, и None — единственный экземпляр типа NoneType. В системе типов Python — аналог null (JavaScript), nil (Ruby), NULL (SQL).

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

def find_user(user_id):
if user_id in database:
return database[user_id]
return None # пользователя нет

None не то же самое, что False, 0 или пустая строка. При проверке в if ведёт себя как False, но не равен ему:

bool(None) # False
None == False # False!

Правильно проверять: if value is None:


Последовательности (Sequences)

Последовательности — упорядоченные коллекции, доступные по индексу.

Это str, list, tuple.

Есть также диапазон range:

<переменная> = range(<конец>)
<переменная> = range(<начало>, <конец>)
<переменная> = range(<начало>, <конец>, <шаг>)

Примеры:

  • indices = range(5)
  • pages = range(1, 11)
  • odds = range(1, 20, 2)

Кортежи (tuple)

Кортежи (tuple) представляют собой неизменяемые списки и часто используются для группировки данных.

<переменная> = (<элемент>, <элемент>, ...)
<переменная> = <элемент>,
<переменная> = tuple(<итерируемый_объект>)

Примеры:

  • point = (10, 20)
  • singleton = (42,)
  • rgb = tuple([255, 128, 0])

Пусть вас не пугает это слово, оно означает "несколько значений":

point = (10, 20)
rgb = (255, 128, 0)

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


Play ITЗагрузка интерактивного демо…


Словарь

Словарь (dict) это упорядоченная (начиная с Python 3.7) коллекция пар "ключ-значение". Аналог Object в JavaScript или HashMap в Java.

Ключи должны быть хэшируемыми (обычно неизменяемыми). Эффективны для поиска по ключу (O(1)).

Простой пример:

person = {"name": "Иван", "age": 28}

Сложный пример:

config = {
key: value.upper() if isinstance(value, str) and key.endswith('_KEY') else value
for key, value in os.environ.items()
if key.startswith('APP_')
}

Разбор:

  • Это dictionary comprehension: новая dict собирается в одном выражении.
  • for key, value in os.environ.items() перебирает пары переменных окружения.
  • if key.startswith('APP_') фильтрует только нужные ключи.
  • Тернарное выражение A if condition else B поднимает регистр для строковых значений ключей, заканчивающихся на _KEY. Это генератор словаря, который фильтрует переменные окружения по префиксу APP_, и если ключ заканчивается на _KEY, преобразует строковое значение в верхний регистр. Демонстрирует мощь компактного синтаксиса и условной логики.
<переменная> = {<ключ>: <значение>, <ключ>: <значение>, ...}
<переменная> = dict(<пары_ключ_значение>)
<переменная> = {<ключ_выражение>: <значение_выражение> for <элемент> in <коллекция> if <условие>}

Примеры:

  • person = {"name": "Иван", "age": 28}
  • config = dict(host="localhost", port=8080)
  • squares = {x: x**2 for x in range(5)}

Операции dict:

ДействиеСинтаксис / метод
Добавить или заменитьd[key] = value, d.update(other), d.setdefault(key, default)
Прочитатьd[key], d.get(key, default)
Удалитьdel d[key], d.pop(key), d.popitem(), d.clear()
Проверить ключkey in d
Ключи / значения / парыd.keys(), d.values(), d.items()

Шпаргалка с примерами для get, update, setdefault, popitem и остальных методов — в разделе Методы словаря главы про коллекции.

Подробнее о преобразованиях — Работа с типами.


Множества

Множества (set и frozenset) хранят уникальные элементы без повторений. Неупорядоченные (до Python 3.7), сейчас порядок зависит от версии.

unique_tags = {"python", "web", "backend"}

set — изменяемое множество.

frozenset — неизменяемое, можно использовать как ключ в словаре.

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

a = {1, 2, 3}
b = {3, 4, 5}
a & b # {3} — пересечение
a | b # {1, 2, 3, 4, 5} — объединение

Изменяемое множество (set):

<переменная> = {<элемент>, <элемент>, ...}
<переменная> = set(<итерируемый_объект>)
<переменная> = {<выражение> for <элемент> in <коллекция> if <условие>}

Примеры:

  • tags = {"python", "web"}
  • unique = set([1, 2, 2, 3])
  • squares = {x**2 for x in range(5)}

Неизменяемое множество (frozenset):

<переменная> = frozenset({<элемент>, <элемент>, ...})
<переменная> = frozenset(<итерируемый_объект>)

Примеры:

  • constants = frozenset({"pi", "e"})
  • immutable_keys = frozenset(user.roles)

Операции set (изменяемый):

ДействиеСинтаксис / метод
Добавитьadd(value)
Удалитьremove(value), discard(value)
Проверить наличиеvalue in s
Объединение / пересечение|, &, - или union, intersection, difference

frozenset — те же операции чтения и теоретико-множественные, без add/remove.


Play ITЗагрузка интерактивного демо…


Список

Список (list) это изменяемая упорядоченная коллекция. Аналог массива в JS.

<переменная> = [<элемент>, <элемент>, ...]
<переменная> = list(<итерируемый_объект>)
<переменная> = [<выражение> for <элемент> in <коллекция> if <условие>]

Примеры:

  • items = [1, 2, 3]
  • chars = list("hello")
  • evens = [x for x in range(10) if x % 2 == 0]

Простой пример:

numbers = [1, 2, 3]

Сложный пример:

filtered_logs = [
{**log, 'timestamp': format_timestamp(log['ts']), 'level_name': LOG_LEVELS[log['level']]}
for log in logs
if log['level'] >= MIN_LEVEL and is_valid_source(log['source'])
]

Это списковое включение с распаковкой словаря (**log), добавлением новых полей, преобразованием уровня логирования и фильтрацией. Результат — новый список с расширенной информацией. Пошагово:

  • for log in logs итерирует исходные записи лога.
  • Условие после if отбрасывает сообщения ниже минимального уровня и из неподходящих источников.
  • {**log, ...} копирует исходные поля словаря и добавляет/переопределяет новые ключи.
  • format_timestamp(log['ts']) и LOG_LEVELS[log['level']] выполняют преобразование "сырого" лога в читаемый формат.

Операции list:

ДействиеСинтаксис / метод
Добавить в конецappend(value)
Вставить по индексуinsert(index, value)
Прочитатьlst[index]
Заменитьlst[index] = value
Удалить по индексуpop(index), del lst[index]
Удалить по значениюremove(value)
Срезlst[start:stop:step]
Развернуть порядокreverse() на месте; копия — lst[::-1], list(reversed(lst))подробнее

Функция

Функция (function) — объект первого класса. Поддерживают декораторы, замыкания, лямбды.

Простой пример:

def greet(name):
return f"Привет, {name}!"

Сложный пример:

Код ITЗагрузка примера кода…

Здесь приведён в пример декоратор высшего порядка, реализующий повторные попытки выполнения функции при ошибках. Позволяет добавить устойчивость к временным сбоям сети. Широко используется в системах, взаимодействующих с внешними API.

Обёртка wrapper(*args, **kwargs) принимает любые аргументы и передаёт их оригинальной функции — подробнее в разделе произвольное число аргументов. Функции мы изучим отдельно.

def <имя_функции>(<параметры>):
return <выражение>

<переменная> = lambda <параметры>: <выражение>
<переменная> = <имя_функции>

Примеры:


Бинарные данные

Бинарные данные нужны для работы с сырыми байтами (например, файлы, сеть, шифрование).

bytes — неизменяемая последовательность байтов

data = b"Hello"
print(data[0]) # 72 (ASCII-код 'H')

bytearray — изменяемая версия

b = bytearray(b"Hello")
b[0] = 104 # заменяем 'H' на 'h'
print(b) # bytearray(b'hello')

Их используют при чтении/записи файлов в бинарном режиме (open(..., 'rb')), работе с сетевыми протоколами, в криптографии и сериализации.

Например, чтобы перевести строку в байты: text.encode('utf-8'), а чтобы перевести байты в строку: data.decode('utf-8').

Байты (bytes):

<переменная> = b"<последовательность_байтов>"
<переменная> = bytes(<итерируемый_объект_целых>)
<переменная> = "<строка>".encode("<кодировка>")

Примеры:

  • data = b"Hello"
  • raw = bytes([72, 101, 108, 108, 111])
  • encoded = "привет".encode("utf-8")

Массив байтов (bytearray):

<переменная> = bytearray(b"<последовательность_байтов>")
<переменная> = bytearray(<итерируемый_объект_целых>)
<переменная> = bytearray("<строка>", "<кодировка>")

Примеры:

  • buffer = bytearray(b"Hello")
  • mutable = bytearray([1, 2, 3])
  • text_bytes = bytearray("текст", "utf-8")