15. Кортежи и диапазоны: простые неизменяемые последовательности
В главе 14 мы изучили списки(list) — универсальный изменяемый тип последовательностей Python. Теперь мы рассмотрим два других важных типа последовательностей: кортежи(tuple) и диапазоны(range). Если списки отлично подходят для хранения коллекций, которые со временем меняются, то кортежи предоставляют неизменяемые последовательности, которые защищают данные от модификации, а диапазоны предлагают экономные по памяти способы представления последовательностей чисел.
Понимание того, когда использовать каждый тип последовательности, сделает ваши программы более эффективными, безопасными и более явно выражающими намерения. К концу этой главы вы будете знать, как эффективно работать с кортежами и диапазонами, и поймёте общие операции, которые работают для всех типов последовательностей Python.
15.1) Создание и использование кортежей (значение запятой)
Кортеж(tuple) — это упорядоченная неизменяемая последовательность элементов. Как и списки, кортежи могут содержать данные любого типа и сохраняют порядок элементов. Однако, в отличие от списков, после создания кортежа вы не можете изменять его содержимое.
Создание кортежей с помощью круглых скобок
Самый распространённый способ создать кортеж — заключить значения, разделённые запятыми, в круглые скобки:
# Кортеж с результатами тестов студентов
scores = (85, 92, 78, 95)
print(scores) # Output: (85, 92, 78, 95)
print(type(scores)) # Output: <class 'tuple'>
# Кортеж со смешанными типами данных
student_info = ("Alice", 20, "Computer Science", 3.8)
print(student_info) # Output: ('Alice', 20, 'Computer Science', 3.8)
# Пустой кортеж
empty = ()
print(empty) # Output: ()
print(len(empty)) # Output: 0Кортежи используют круглые скобки () в качестве литерального синтаксиса, тогда как списки используют квадратные скобки []. Это визуальное различие помогает сразу понимать, с каким типом вы работаете.
Кортеж создаёт запятая, а не скобки
Вот важная деталь, которая удивляет многих новичков: на самом деле кортеж создаёт запятая, а не скобки. Скобки часто необязательны и в основном служат для того, чтобы кортеж был более заметным, или для группировки в выражениях.
# Все эти варианты создают один и тот же кортеж
coordinates_1 = (10, 20)
coordinates_2 = 10, 20 # Скобки не нужны!
print(coordinates_1) # Output: (10, 20)
print(coordinates_2) # Output: (10, 20)
print(coordinates_1 == coordinates_2) # Output: True
# Важна именно запятая
x = (42) # Это просто целое число 42 в скобках
y = (42,) # Это кортеж, содержащий один элемент
print(type(x)) # Output: <class 'int'>
print(type(y)) # Output: <class 'tuple'>
print(y) # Output: (42,)Скобки в (42) — это просто скобки для группировки, как в математических выражениях. Чтобы создать кортеж из одного элемента, вы обязательно должны добавить завершающую запятую: (42,). Эта запятая сообщает Python, что вам нужен кортеж, а не просто сгруппированное выражение.
Когда скобки обязательны
Хотя кортеж создаёт запятая, в некоторых ситуациях скобки становятся необходимыми, чтобы избежать неоднозначности:
# Без скобок это выглядело бы неоднозначно
def get_dimensions():
return 1920, 1080 # Returns a tuple
width, height = get_dimensions()
print(f"Screen: {width}x{height}") # Output: Screen: 1920x1080
# Скобки нужны при передаче кортежей как аргументов функции
print((1, 2, 3)) # Output: (1, 2, 3)
# Без скобок Python увидел бы три отдельные аргумента
# Скобки нужны в сложных выражениях
result = (10, 20) + (30, 40) # Конкатенация кортежей
print(result) # Output: (10, 20, 30, 40)Создание одноэлементных кортежей
Требование о завершающей запятой в одноэлементных кортежах часто застает новичков врасплох:
# Частая ошибка: забыть запятую
not_a_tuple = ("Python")
print(type(not_a_tuple)) # Output: <class 'str'>
print(not_a_tuple) # Output: Python
# Правильно: добавьте завершающую запятую
is_a_tuple = ("Python",)
print(type(is_a_tuple)) # Output: <class 'tuple'>
print(is_a_tuple) # Output: ('Python',)
# Запятая работает даже без скобок
also_a_tuple = "Python",
print(type(also_a_tuple)) # Output: <class 'tuple'>
print(also_a_tuple) # Output: ('Python',)Почему Python требует этого, казалось бы, неудобного синтаксиса? Потому что круглые скобки уже имеют другое значение в Python — они группируют выражения. Без запятой Python никак не может различить (42) как сгруппированное число и (42) как кортеж.
Доступ к элементам кортежа
Кортежи поддерживают те же операции индексации и срезов, что и списки:
# Кортеж с информацией о студенте
student = ("Bob", 22, "Physics", 3.6)
# Доступ к отдельным элементам (индексация с нуля)
name = student[0]
age = student[1]
major = student[2]
gpa = student[3]
print(f"{name} is {age} years old") # Output: Bob is 22 years old
print(f"Major: {major}, GPA: {gpa}") # Output: Major: Physics, GPA: 3.6
# Отрицательная индексация тоже работает
last_item = student[-1]
print(f"Last item: {last_item}") # Output: Last item: 3.6
# Срез извлекает новый кортеж
first_two = student[:2]
print(first_two) # Output: ('Bob', 22)
print(type(first_two)) # Output: <class 'tuple'>Каждая техника индексации и нарезки срезами, которую вы изучили для списков в главе 14, работает с кортежами точно так же. Ключевое отличие в том, что кортежи нельзя изменять после создания.
15.2) Упаковка и распаковка кортежей
Одна из самых мощных и элегантных возможностей кортежей — их способность упаковывать несколько значений вместе и распаковывать их в отдельные переменные. Эта возможность делает код Python удивительно лаконичным и читаемым.
Упаковка кортежа
Упаковка кортежа(tuple packing) происходит, когда вы создаёте кортеж, размещая несколько значений вместе, разделяя их запятыми:
# Упаковка значений в кортеж
coordinates = 10, 20, 30
print(coordinates) # Output: (10, 20, 30)
# Упаковка разных типов
user_data = "Alice", 25, "alice@example.com"
print(user_data) # Output: ('Alice', 25, 'alice@example.com')
# Упаковка возвращаемых значений функции
def get_statistics(numbers):
total = sum(numbers)
count = len(numbers)
average = total / count
return total, count, average # Упаковывает три значения в кортеж
stats = get_statistics([85, 90, 78, 92, 88])
print(stats) # Output: (433, 5, 86.6)Когда функция возвращает несколько значений, разделённых запятыми, Python автоматически упаковывает их в кортеж. Поэтому может казаться, что функции возвращают несколько значений — на самом деле они возвращают один кортеж, содержащий эти значения.
Распаковка кортежа
Распаковка кортежа(tuple unpacking) — обратный процесс: извлечение значений из кортежа в отдельные переменные:
# Базовая распаковка
point = (100, 200)
x, y = point
print(f"x = {x}, y = {y}") # Output: x = 100, y = 200
# Распаковка работает с любой последовательностью, не только с кортежами
name, age, email = ["Bob", 30, "bob@example.com"]
print(f"{name} is {age} years old") # Output: Bob is 30 years old
# Распаковка возвращаемых значений функции напрямую
total, count, average = get_statistics([95, 88, 92, 85])
print(f"Average of {count} scores: {average}") # Output: Average of 4 scores: 90.0Количество переменных слева должно совпадать с количеством элементов в последовательности. Если они не совпадают, Python вызывает ValueError:
# Это вызовет ошибку
coordinates = (10, 20, 30)
# x, y = coordinates # ValueError: too many values to unpack (expected 2)
# Это тоже вызовет ошибку
point = (5, 10)
# x, y, z = point # ValueError: not enough values to unpack (expected 3, got 2)Обмен значений переменных с помощью распаковки кортежа
Распаковка кортежа позволяет элегантно менять местами значения переменных без временной переменной:
# Традиционный обмен с использованием временной переменной
a = 10
b = 20
temp = a
a = b
b = temp
print(f"a = {a}, b = {b}") # Output: a = 20, b = 10
# Элегантный обмен в Python с помощью распаковки кортежа
x = 100
y = 200
x, y = y, x # Swap in one line!
print(f"x = {x}, y = {y}") # Output: x = 200, y = 100
# Обмен более чем двух переменных
first = "A"
second = "B"
third = "C"
first, second, third = third, first, second
print(first, second, third) # Output: C A BКак это работает? Python сначала вычисляет правую часть, создавая кортеж (y, x), затем распаковывает его в переменные слева. Это происходит за один шаг, поэтому временная переменная не нужна.
Расширенная распаковка со звёздочкой
Python предоставляет расширенную распаковку(extended unpacking) с использованием оператора * для захвата нескольких элементов:
# Распаковка с переменной для "остатка"
scores = (95, 88, 92, 85, 90, 87)
first, second, *rest = scores
print(f"Top two: {first}, {second}") # Output: Top two: 95, 88
print(f"Others: {rest}") # Output: Others: [92, 85, 90, 87]
print(type(rest)) # Output: <class 'list'>
# Звёздочка может стоять где угодно
numbers = (1, 2, 3, 4, 5)
first, *middle, last = numbers
print(f"First: {first}") # Output: First: 1
print(f"Middle: {middle}") # Output: Middle: [2, 3, 4]
print(f"Last: {last}") # Output: Last: 5
# Захват начала
*beginning, second_last, last = numbers
print(f"Beginning: {beginning}") # Output: Beginning: [1, 2, 3]
print(f"Last two: {second_last}, {last}") # Output: Last two: 4, 5Обратите внимание, что переменная со звёздочкой всегда захватывает элементы как список(list), даже если распаковка идёт из кортежа. Если элементов нечего захватывать, переменная со звёздочкой становится пустым списком:
# Когда захватывать нечего
a, b, *rest = (10, 20)
print(rest) # Output: []
# В одной распаковке допускается только одна звёздочка
# first, *middle, *end = (1, 2, 3, 4) # SyntaxError: multiple starred expressionsИгнорирование значений с помощью подчёркивания
Иногда вам нужны только определённые значения из кортежа. По соглашению программисты Python используют подчёркивание _ как имя переменной, чтобы обозначить значения, которые они хотят игнорировать:
# Разбор строки даты
date_string = "2024-03-15"
year, month, day = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# Если нас интересует только месяц
_, month, _ = date_string.split("-")
print(f"Month: {month}") # Output: Month: 03
# С расширенной распаковкой
data = ("Alice", 25, "Engineer", "New York", "alice@example.com")
name, age, *_, email = data
print(f"{name} ({age}): {email}") # Output: Alice (25): alice@example.comПодчёркивание — это обычное имя переменной, но его использование сигнализирует другим программистам (и вам самим), что вы намеренно игнорируете эти значения.
Практические примеры упаковки и распаковки
# Возврат нескольких значений из вычислений
def calculate_rectangle_properties(width, height):
"""Вычислить площадь и периметр прямоугольника."""
area = width * height
perimeter = 2 * (width + height)
return area, perimeter # Упаковка
# Распаковка результатов
rect_area, rect_perimeter = calculate_rectangle_properties(5, 3)
print(f"Area: {rect_area}, Perimeter: {rect_perimeter}") # Output: Area: 15, Perimeter: 16
# Итерация с распаковкой
students = [
("Alice", 85),
("Bob", 92),
("Carol", 78)
]
for name, score in students: # Распаковка в цикле
print(f"{name}: {score}")
# Output:
# Alice: 85
# Bob: 92
# Carol: 78Упаковка и распаковка кортежей делают код Python более читаемым и выразительным. Вместо доступа к элементам кортежа по индексу (student[0], student[1]) вы можете распаковать их в осмысленно названные переменные.
15.3) Кортежи неизменяемы: когда это полезно
Определяющая характеристика кортежей — их неизменяемость(immutability): после создания содержимое кортежа нельзя изменить. Вы не можете добавлять, удалять или модифицировать элементы. Эта неизменяемость может показаться ограничением, но она даёт важные преимущества.
Что неизменяемость означает на практике
# Создание кортежа
coordinates = (10, 20, 30)
print(coordinates) # Output: (10, 20, 30)
# Попытка изменить вызывает ошибку
# coordinates[0] = 15 # TypeError: 'tuple' object does not support item assignment
# Попытка добавить элементы вызывает ошибку
# coordinates.append(40) # AttributeError: 'tuple' object has no attribute 'append'
# Попытка удалить элементы вызывает ошибку
# del coordinates[1] # TypeError: 'tuple' object doesn't support item deletionКогда Python говорит, что кортежи не поддерживают присваивание элемента, это означает, что вы не можете изменить то, что хранится на любой позиции в кортеже. Структура кортежа фиксируется при создании.
Сравнение изменяемых списков и неизменяемых кортежей
# Списки изменяемы — вы можете их менять
shopping_list = ["milk", "bread", "eggs"]
shopping_list[1] = "butter" # Изменить элемент
shopping_list.append("cheese") # Добавить элемент
print(shopping_list) # Output: ['milk', 'butter', 'eggs', 'cheese']
# Кортежи неизменяемы — вы не можете их менять
product_dimensions = (10, 20, 5) # width, height, depth in cm
# product_dimensions[0] = 12 # TypeError: cannot modify
# product_dimensions.append(3) # AttributeError: no append method
# Чтобы "изменить" кортеж, нужно создать новый
new_dimensions = (12, 20, 5) # Создать полностью новый кортеж
print(new_dimensions) # Output: (12, 20, 5)Почему неизменяемость полезна
Неизменяемость даёт несколько практических преимуществ:
1. Целостность и безопасность данных
Когда вы передаёте кортеж в функцию, вы знаете, что функция не может случайно изменить ваши данные:
def calculate_distance(point1, point2):
"""Вычислить расстояние между двумя точками 2D."""
x1, y1 = point1
x2, y2 = point2
dx = x2 - x1
dy = y2 - y1
# Даже если бы мы захотели, мы не можем изменить входные кортежи
return (dx**2 + dy**2) ** 0.5
start = (0, 0)
end = (3, 4)
distance = calculate_distance(start, end)
print(f"Distance: {distance}") # Output: Distance: 5.0
print(f"Start point unchanged: {start}") # Output: Start point unchanged: (0, 0)Со списками вам пришлось бы беспокоиться, не изменит ли функция ваши данные. С кортежами у вас есть гарантия, что этого не произойдёт.
2. Использование кортежей как ключей словаря
Как мы подробнее рассмотрим в главе 17, ключи словаря должны быть хешируемыми(hashable) — у них должно быть хеш-значение, которое никогда не меняется. Неизменяемые объекты, такие как кортежи, могут быть ключами словаря; изменяемые объекты, такие как списки, — нет:
# Кортежи могут быть ключами словаря
locations = {
(0, 0): "Origin",
(10, 20): "Point A",
(30, 40): "Point B"
}
print(locations[(10, 20)]) # Output: Point A
# Списки не могут быть ключами словаря
# locations_bad = {
# [0, 0]: "Origin" # TypeError: unhashable type: 'list'
# }3. Сигнал намерения
Использование кортежа вместо списка сообщает другим программистам (и вам самим), что эти данные не должны меняться:
# Значения цветов RGB — они не должны меняться
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# Параметры подключения к базе данных — фиксированная конфигурация
DB_CONFIG = ("localhost", 5432, "myapp", "production")
# Географические координаты — местоположение не меняется
EIFFEL_TOWER = (48.8584, 2.2945) # latitude, longitudeКогда вы видите кортеж в коде, вы сразу понимаете, что эти данные задуманы как константные. Когда вы видите список, вы понимаете, что он может быть изменён.
4. Преимущества производительности
Поскольку кортежи неизменяемы, Python может оптимизировать их так, как не может оптимизировать списки. Мы изучим модуль sys в главе 27, но сейчас достаточно знать, что sys.getsizeof() показывает, сколько памяти использует объект:
import sys
# Кортежи используют меньше памяти, чем эквивалентные списки
tuple_data = (1, 2, 3, 4, 5)
list_data = [1, 2, 3, 4, 5]
print(f"Tuple size: {sys.getsizeof(tuple_data)} bytes") # Output: Tuple size: 80 bytes (may vary by Python version)
print(f"List size: {sys.getsizeof(list_data)} bytes") # Output: List size: 104 bytes (may vary by Python version)
# Создание кортежа быстрее
import timeit
tuple_time = timeit.timeit("(1, 2, 3, 4, 5)", number=1000000)
list_time = timeit.timeit("[1, 2, 3, 4, 5]", number=1000000)
print(f"Tuple creation: {tuple_time:.4f} seconds")
print(f"List creation: {list_time:.4f} seconds")
# Example output: Tuple creation: 0.0055 seconds, List creation: 0.0292 seconds15.4) Ловушка неизменяемости: когда кортежи содержат изменяемые элементы
Хотя сами кортежи неизменяемы, они могут содержать изменяемые объекты, такие как списки или словари. Это создаёт тонкое, но важное различие: структура кортежа фиксирована, но содержимое изменяемых объектов внутри него всё ещё может меняться.
Понимание различия
# Кортеж, содержащий список
student_data = ("Alice", 20, [85, 90, 78]) # name, age, scores
print(student_data) # Output: ('Alice', 20, [85, 90, 78])
# Мы не можем переназначать элементы кортежа
# student_data[0] = "Bob" # TypeError: 'tuple' object does not support item assignment
# Но мы МОЖЕМ изменить список внутри кортежа
student_data[2].append(92) # Добавить новый балл
print(student_data) # Output: ('Alice', 20, [85, 90, 78, 92])
student_data[2][0] = 88 # Изменить существующий балл
print(student_data) # Output: ('Alice', 20, [88, 90, 78, 92])Что здесь происходит? Кортеж хранит три ссылки: одну на строку "Alice", одну на целое число 20 и одну на объект списка. Структура кортежа — на какие объекты он ссылается — не может измениться. Но сам объект списка изменяем, поэтому его содержимое может меняться.
Визуализация различия
# Структура кортежа фиксирована
data = ("Python", [1, 2, 3])
# Это пытается изменить то, на что ссылается кортеж — НЕЛЬЗЯ
# data[1] = [4, 5, 6] # TypeError
# Это изменяет список, на который ссылается кортеж — МОЖНО
data[1].append(4)
print(data) # Output: ('Python', [1, 2, 3, 4])
# Кортеж по-прежнему ссылается на тот же объект списка
# Изменилось только содержимое списка, а не то, на какой список указывает кортежДумайте об этом так: кортеж — это как ряд коробок, и в каждой коробке лежит ссылка на объект. Сами коробки «заперты» на месте (неизменяемы), но если коробка содержит ссылку на изменяемый объект, этот объект всё равно может меняться.
Кортежи со словарями
Тот же принцип применим к словарям внутри кортежей:
# Кортеж, содержащий словарь
user_profile = ("alice", {"email": "alice@example.com", "age": 25})
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 25})
# Нельзя изменить, на какой словарь ссылается кортеж
# user_profile[1] = {"email": "newemail@example.com"} # TypeError
# Но МОЖНО изменить сам словарь
user_profile[1]["age"] = 26
user_profile[1]["city"] = "New York"
print(user_profile) # Output: ('alice', {'email': 'alice@example.com', 'age': 26, 'city': 'New York'})Почему это важно для ключей словаря
Кортежи можно использовать как ключи словаря только в том случае, если все их элементы хешируемы. Хотя сами кортежи неизменяемы, кортеж, содержащий изменяемые объекты (например, списки), вообще не является хешируемым и поэтому не может быть использован как ключ словаря.
# Это работает, но опасно
tuple_with_list = ("key", [1, 2, 3])
# data = {tuple_with_list: "value"} # TypeError: unhashable type: 'list'Используйте в качестве ключей словаря только кортежи, содержащие полностью неизменяемые объекты (строки, числа, frozenset, другие кортежи).
Создание действительно неизменяемых кортежей
Если вам нужен кортеж, который полностью неизменяем, убедитесь, что всё его содержимое тоже неизменяемо:
# Полностью неизменяемый кортеж — только неизменяемые типы
point_3d = (10, 20, 30) # Все целые числа
rgb_color = (255, 128, 0) # Все целые числа
coordinates = ((10, 20), (30, 40)) # Кортеж кортежей
# Их безопасно использовать как ключи словаря
color_names = {
(255, 0, 0): "Red",
(0, 255, 0): "Green",
(0, 0, 255): "Blue"
}
# Вложенные кортежи остаются неизменяемыми
nested = ((1, 2), (3, 4))
# nested[0][0] = 5 # TypeError: 'tuple' object does not support item assignmentКогда изменяемое содержимое намеренно
Иногда вам действительно нужен кортеж с изменяемым содержимым — например, когда у вас есть фиксированная структура записи, но одно поле должно меняться:
# Запись студента с фиксированной идентичностью, но меняющимися оценками
def create_student(name, student_id):
"""Создать запись студента с пустым списком оценок."""
return (name, student_id, []) # name и ID фиксированы, grades могут меняться
student = create_student("Alice", "S12345")
print(student) # Output: ('Alice', 'S12345', [])
# Идентичность студента фиксирована
print(f"Student: {student[0]} (ID: {student[1]})") # Output: Student: Alice (ID: S12345)
# Но мы можем добавлять оценки по мере их получения
student[2].append(85)
student[2].append(92)
student[2].append(78)
print(f"Grades: {student[2]}") # Output: Grades: [85, 92, 78]
# Структура кортежа защищает name и ID от случайных изменений,
# одновременно позволяя списку оценок растиЭтот шаблон полезен, когда вы хотите защитить одни данные, одновременно позволяя другим данным меняться. Просто помните о различии между неизменяемостью кортежа и изменяемостью его содержимого.
15.5) Когда использовать кортежи вместо списков
Выбор между кортежами и списками — важное проектное решение. Хотя и то и другое — последовательности, они служат разным целям и передают разные намерения.
Используйте кортежи для фиксированных, разнородных данных
Кортежи лучше всего подходят, когда у вас фиксированное число элементов, представляющих одну логическую сущность, часто с разными типами:
# Запись студента: имя, возраст, специальность, средний балл
student = ("Alice", 20, "Computer Science", 3.8)
# Географические координаты: широта, долгота
location = (40.7128, -74.0060) # New York City
# Цвет RGB: красный, зелёный, синий
color = (255, 128, 0)
# Подключение к базе данных: host, port, database, username
db_connection = ("localhost", 5432, "myapp", "admin")
# Дата: год, месяц, день
date = (2024, 3, 15)Каждый кортеж представляет полноценную «запись», где позиция каждого элемента имеет определённый смысл. Первый элемент — всегда имя, второй — всегда возраст, и так далее.
Используйте списки для однородных коллекций
Списки лучше всего подходят, когда у вас переменное количество похожих элементов, которые вы можете добавлять, удалять или переупорядочивать:
# Список покупок — элементы одного типа (строки)
shopping_list = ["milk", "bread", "eggs", "butter"]
shopping_list.append("cheese") # Добавлять элементы по мере необходимости
shopping_list.remove("bread") # Удалять элементы
# Результаты тестов — элементы одного типа (числа)
test_scores = [85, 92, 78, 95, 88]
test_scores.append(90) # Добавить новый балл
test_scores.sort() # Переупорядочить баллы
# Имена пользователей — элементы одного типа (строки)
active_users = ["alice", "bob", "carol"]
active_users.extend(["dave", "eve"]) # Добавить несколько пользователейСписки предназначены для коллекций, где количество элементов может меняться и где каждый элемент играет одну и ту же роль.
Кортежи для возвращаемых значений функций
Когда функция возвращает несколько связанных значений, кортежи — естественный выбор:
def get_user_info(user_id):
"""Получить информацию о пользователе из базы данных."""
# Имитация поиска в базе данных
return "Alice", "alice@example.com", 25, "New York"
# Распаковать возвращаемый кортеж
name, email, age, city = get_user_info(101)
print(f"{name} from {city}") # Output: Alice from New York
def calculate_statistics(numbers):
"""Вычислить min, max и среднее для чисел."""
if not numbers:
return None, None, None
minimum = min(numbers)
maximum = max(numbers)
average = sum(numbers) / len(numbers)
return minimum, maximum, average
# Распаковать результаты
min_val, max_val, avg_val = calculate_statistics([85, 92, 78, 95, 88])
print(f"Range: {min_val} to {max_val}, Average: {avg_val}")
# Output: Range: 78 to 95, Average: 87.6Возврат кортежей ясно показывает, что эти значения связаны и должны рассматриваться вместе.
Кортежи для ключей словаря
Когда вам нужны составные ключи в словаре, кортежи незаменимы:
# Оценки студентов по курсу и семестру
grades = {
("CS101", "Fall2023"): 85,
("CS101", "Spring2024"): 90,
("MATH201", "Fall2023"): 88,
("MATH201", "Spring2024"): 92
}
# Найти конкретную оценку
course = "CS101"
semester = "Spring2024"
grade = grades[(course, semester)]
print(f"Grade in {course} ({semester}): {grade}") # Output: Grade in CS101 (Spring2024): 90
# Координаты сетки в качестве ключей словаря
grid = {
(0, 0): "Start",
(5, 3): "Obstacle",
(10, 10): "Goal"
}
position = (5, 3)
if position in grid:
print(f"At {position}: {grid[position]}") # Output: At (5, 3): ObstacleСписки не могут быть ключами словаря, потому что они изменяемы, а кортежи — могут.
Кортежи для неизменяемой конфигурации
Когда у вас есть данные конфигурации, которые никогда не должны меняться, кортежи сигнализируют об этом намерении:
# Настройки приложения, которые должны оставаться постоянными
APP_CONFIG = (
"MyApp", # Application name
"1.0.0", # Version
"production", # Environment
True, # Debug mode
8080 # Port
)
# Палитра цветов для UI — эти цвета фиксированы
COLOR_PALETTE = (
(255, 0, 0), # Primary red
(0, 128, 255), # Primary blue
(255, 255, 255), # White
(0, 0, 0) # Black
)
# Эндпоинты API — эти URL не меняются
API_ENDPOINTS = (
"https://api.example.com/users",
"https://api.example.com/products",
"https://api.example.com/orders"
)Памятка для выбора
# Используйте КОРТЕЖИ, когда:
# 1. Данные представляют одну запись с фиксированной структурой
employee = ("E001", "Alice", "Engineering", 75000)
# 2. Возвращаете несколько значений из функции
def divide_with_remainder(a, b):
return a // b, a % b
# 3. Нужно использовать в качестве ключей словаря
cache = {(5, 10): 50, (3, 7): 21}
# 4. Данные не должны изменяться
SCREEN_RESOLUTION = (1920, 1080)
# Используйте СПИСКИ, когда:
# 1. Коллекция похожих элементов, которая может меняться
tasks = ["Write code", "Test code", "Deploy code"]
tasks.append("Document code")
# 2. Нужно добавлять, удалять или переупорядочивать элементы
scores = [85, 90, 78]
scores.sort()
scores.append(92)
# 3. Все элементы служат одной цели
usernames = ["alice", "bob", "carol"]
# 4. Размер коллекции заранее неизвестен
results = []
for i in range(10):
results.append(i * 2)15.6) Подробное понимание объектов range
Теперь, когда мы понимаем, когда использовать кортежи вместо списков, давайте рассмотрим третий неизменяемый тип последовательности Python: диапазоны(range). Тип range представляет неизменяемую последовательность чисел. В отличие от списков и кортежей, которые хранят все элементы в памяти, объекты range генерируют числа по мере необходимости, что делает их чрезвычайно экономными по памяти для представления больших последовательностей.
Создание объектов range
Функция range() создаёт объекты range в трёх формах:
# Один аргумент: range(stop)
# Генерирует числа от 0 до stop (не включая stop)
numbers = range(5)
print(list(numbers)) # Output: [0, 1, 2, 3, 4]
# Два аргумента: range(start, stop)
# Генерирует числа от start до stop (не включая stop)
numbers = range(2, 7)
print(list(numbers)) # Output: [2, 3, 4, 5, 6]
# Три аргумента: range(start, stop, step)
# Генерирует числа от start до stop, увеличивая на step
numbers = range(0, 10, 2)
print(list(numbers)) # Output: [0, 2, 4, 6, 8]
# Отрицательный шаг для обратного счёта
numbers = range(10, 0, -1)
print(list(numbers)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]Обратите внимание, что мы преобразуем диапазоны в списки с помощью list(), чтобы увидеть их содержимое. Сам объект range не отображает все значения при печати:
r = range(5)
print(r) # Output: range(0, 5)
print(type(r)) # Output: <class 'range'>Как работают объекты range
Объекты range не хранят все свои значения в памяти. Вместо этого они вычисляют каждое значение по мере необходимости:
import sys
# Диапазон, представляющий миллион чисел
large_range = range(1000000)
print(f"Range size: {sys.getsizeof(large_range)} bytes") # Output: Range size: 48 bytes (may vary by Python version)
# Список, содержащий миллион чисел
large_list = list(range(1000000))
print(f"List size: {sys.getsizeof(large_list)} bytes") # Output: List size: 8000056 bytes (approximately 8MB)
# Диапазон крошечный; список огромный!Объект range хранит только три значения: start, stop и step. Он вычисляет каждое число последовательности, когда вы его запрашиваете. Это делает диапазоны невероятно эффективными для больших последовательностей.
Использование range в циклах for
Как мы узнали в главе 12, диапазоны чаще всего используются с циклами(loop) for:
# Счёт от 0 до 4
for i in range(5):
print(f"Count: {i}")
# Output:
# Count: 0
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Счёт от 1 до 10
for i in range(1, 11):
print(i, end=" ")
print() # Output: 1 2 3 4 5 6 7 8 9 10
# Счёт по два
for i in range(0, 20, 2):
print(i, end=" ")
print() # Output: 0 2 4 6 8 10 12 14 16 18
# Обратный отсчёт
for i in range(5, 0, -1):
print(f"T-minus {i}")
# Output:
# T-minus 5
# T-minus 4
# T-minus 3
# T-minus 2
# T-minus 1Индексация и срезы объектов range
Объекты range поддерживают индексацию и срезы так же, как и другие последовательности:
# Создание range
numbers = range(10, 50, 5) # 10, 15, 20, 25, 30, 35, 40, 45
# Индексация
print(numbers[0]) # Output: 10
print(numbers[3]) # Output: 25
print(numbers[-1]) # Output: 45
# Срез возвращает новый range
subset = numbers[2:5]
print(subset) # Output: range(20, 35, 5)
print(list(subset)) # Output: [20, 25, 30]
# Длина
print(len(numbers)) # Output: 8Проверка принадлежности
Вы можете проверить, входит ли число в диапазон, используя оператор in:
# Чётные числа от 0 до 20
evens = range(0, 21, 2)
print(10 in evens) # Output: True
print(15 in evens) # Output: False
print(20 in evens) # Output: True
# Это очень эффективно — Python не генерирует все числа
# Он вычисляет, могло ли число быть в последовательности
large_range = range(0, 1000000, 3)
print(999999 in large_range) # Output: True (instant, no iteration needed)Python может определить принадлежность математически, не генерируя все числа, что делает эту операцию чрезвычайно быстрой даже для огромных диапазонов.
Пустые и обратные диапазоны
# Пустой диапазон — stop равен start
empty = range(5, 5)
print(list(empty)) # Output: []
print(len(empty)) # Output: 0
# Пустой диапазон — невозможно дойти до stop с заданным шагом
impossible = range(1, 10, -1) # Нельзя считать вверх с отрицательным шагом
print(list(impossible)) # Output: []
# Обратный диапазон
backwards = range(10, 0, -1)
print(list(backwards)) # Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# Обратный диапазон с отрицательными числами
negative_range = range(-5, -15, -2)
print(list(negative_range)) # Output: [-5, -7, -9, -11, -13]Когда использовать range вместо списков
# Используйте range, когда:
# 1. Нужна последовательность чисел для итерации
for i in range(100):
# Обработать что-то 100 раз
pass
# 2. Нужны индексы для последовательности
items = ["a", "b", "c", "d"]
for i in range(len(items)):
print(f"Index {i}: {items[i]}")
# 3. Важна экономия памяти на больших последовательностях
# Это использует минимальную память
for i in range(1000000):
if i % 100000 == 0:
print(i)
# Используйте списки, когда:
# 1. Нужно хранить реальные значения
squares = [1, 3, 5, 7, 10]
# 2. Нужно изменять последовательность
numbers = list(range(5))
numbers[2] = 100 # Изменить значение
numbers.append(200) # Добавить значение
# 3. Нужно использовать последовательность многократно с разными операциями
data = list(range(10))
print(sum(data))
print(max(data))
print(sorted(data, reverse=True))Объекты range — отличный пример эффективности Python. Они предоставляют все преимущества последовательности без затрат памяти на хранение каждого элемента.
15.7) Преобразование между списками, кортежами и диапазонами
Python позволяет легко преобразовывать разные типы последовательностей. Понимание этих преобразований помогает выбирать правильный тип для каждой ситуации и при необходимости трансформировать данные.
Преобразование в списки
Функция list() преобразует любую последовательность в список:
# Кортеж в список
student_tuple = ("Alice", 20, "CS")
student_list = list(student_tuple)
print(student_list) # Output: ['Alice', 20, 'CS']
print(type(student_list)) # Output: <class 'list'>
# Теперь мы можем его изменить
student_list[1] = 21
student_list.append(3.8)
print(student_list) # Output: ['Alice', 21, 'CS', 3.8]
# Range в список
numbers = range(5)
numbers_list = list(numbers)
print(numbers_list) # Output: [0, 1, 2, 3, 4]
# Строка в список (каждый символ становится элементом)
text = "Python"
chars = list(text)
print(chars) # Output: ['P', 'y', 't', 'h', 'o', 'n']Преобразование в список полезно, когда нужно изменять последовательность или когда нужно использовать методы, специфичные для списков, такие как append(), sort() или remove().
Преобразование в кортежи
Функция tuple() преобразует любую последовательность в кортеж:
# Список в кортеж
scores_list = [85, 90, 78, 92]
scores_tuple = tuple(scores_list)
print(scores_tuple) # Output: (85, 90, 78, 92)
print(type(scores_tuple)) # Output: <class 'tuple'>
# Теперь он неизменяемый
# scores_tuple[0] = 88 # TypeError: 'tuple' object does not support item assignment
# Range в кортеж
numbers = range(1, 6)
numbers_tuple = tuple(numbers)
print(numbers_tuple) # Output: (1, 2, 3, 4, 5)
# Строка в кортеж
text = "Hi"
chars_tuple = tuple(text)
print(chars_tuple) # Output: ('H', 'i')Преобразование в кортеж полезно, когда вы хотите защитить данные от модификации или когда нужно использовать последовательность в качестве ключа словаря.
15.8) Общие операции над последовательностями для строк, списков, кортежей и диапазонов
Типы последовательностей Python — строки, списки, кортежи и диапазоны — разделяют множество общих операций. Понимание этих общих операций помогает эффективно работать с любым типом последовательности.
Длина, минимум и максимум
Все последовательности поддерживают функции len(), min() и max():
# Строки
text = "Python"
print(len(text)) # Output: 6
print(min(text)) # Output: P (smallest character by Unicode value)
print(max(text)) # Output: y (largest character by Unicode value)
# Списки
numbers = [45, 12, 78, 23, 56]
print(len(numbers)) # Output: 5
print(min(numbers)) # Output: 12
print(max(numbers)) # Output: 78
# Кортежи
scores = (85, 92, 78, 95, 88)
print(len(scores)) # Output: 5
print(min(scores)) # Output: 78
print(max(scores)) # Output: 95
# Диапазоны
nums = range(10, 50, 5)
print(len(nums)) # Output: 8
print(min(nums)) # Output: 10
print(max(nums)) # Output: 45Чтобы min() и max() работали, элементы должны быть сравнимы. Нельзя найти минимум списка, содержащего и строки, и числа:
mixed = [1, "hello", 3]
# print(min(mixed)) # TypeError: '<' not supported between instances of 'str' and 'int'Индексация и отрицательная индексация
Все последовательности поддерживают индексацию с положительными и отрицательными индексами:
# Положительная индексация (с 0)
text = "Python"
numbers = [10, 20, 30, 40, 50]
coords = (5, 10, 15)
values = range(0, 100, 10)
print(text[0]) # Output: P
print(numbers[2]) # Output: 30
print(coords[1]) # Output: 10
print(values[3]) # Output: 30
# Отрицательная индексация (с конца)
print(text[-1]) # Output: n (last character)
print(numbers[-2]) # Output: 40 (second from end)
print(coords[-3]) # Output: 5 (third from end, which is first)
print(values[-1]) # Output: 90 (last value in range)Отрицательные индексы считаются с конца: -1 — последний элемент, -2 — предпоследний и так далее.
Проверка принадлежности с помощью in и not in
Все последовательности поддерживают проверку принадлежности:
# Строки — проверка подстрок
text = "Python Programming"
print("Python" in text) # Output: True
print("Java" in text) # Output: False
print("gram" in text) # Output: True (substring)
print("PYTHON" not in text) # Output: True (case-sensitive)
# Списки
fruits = ["apple", "banana", "cherry", "date"]
print("banana" in fruits) # Output: True
print("grape" in fruits) # Output: False
print("apple" not in fruits) # Output: False
# Кортежи
coordinates = (10, 20, 30, 40)
print(20 in coordinates) # Output: True
print(25 in coordinates) # Output: False
print(50 not in coordinates) # Output: True
# Диапазоны — очень эффективно, без итерации
numbers = range(0, 100, 2) # Even numbers 0 to 98
print(50 in numbers) # Output: True
print(51 in numbers) # Output: False (odd number)
print(100 in numbers) # Output: False (stop is exclusive)Для диапазонов Python может определить принадлежность математически, не проверяя каждый элемент, поэтому это работает очень быстро даже для огромных диапазонов.
Конкатенация и повторение
Строки, списки и кортежи поддерживают конкатенацию через + и повторение через *:
# Конкатенация с +
text1 = "Hello"
text2 = " World"
print(text1 + text2) # Output: Hello World
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print(list1 + list2) # Output: [1, 2, 3, 4, 5, 6]
tuple1 = (10, 20)
tuple2 = (30, 40)
print(tuple1 + tuple2) # Output: (10, 20, 30, 40)
# Повторение с *
print("Ha" * 3) # Output: HaHaHa
print([0] * 5) # Output: [0, 0, 0, 0, 0]
print((1, 2) * 3) # Output: (1, 2, 1, 2, 1, 2)Важно: диапазоны не поддерживают конкатенацию или повторение:
r1 = range(5)
r2 = range(5, 10)
# combined = r1 + r2 # TypeError: unsupported operand type(s) for +: 'range' and 'range'
# Чтобы объединить диапазоны, сначала преобразуйте их в списки или кортежи
combined = list(r1) + list(r2)
print(combined) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]Подсчёт вхождений
Метод count() возвращает, сколько раз элемент встречается:
# Строки — считает вхождения подстроки
text = "Mississippi"
print(text.count("s")) # Output: 4
print(text.count("ss")) # Output: 2
print(text.count("i")) # Output: 4
# Списки
numbers = [1, 2, 3, 2, 4, 2, 5]
print(numbers.count(2)) # Output: 3
print(numbers.count(6)) # Output: 0
# Кортежи
grades = (85, 90, 85, 92, 85, 88)
print(grades.count(85)) # Output: 3
print(grades.count(95)) # Output: 0
# У диапазонов нет метода count(), но можно сначала преобразовать
nums = range(0, 20, 2)
nums_list = list(nums)
print(nums_list.count(10)) # Output: 1Поиск индекса элементов
Метод index() возвращает позицию первого вхождения:
# Строки
text = "Python Programming"
print(text.index("P")) # Output: 0 (first P)
print(text.index("Pro")) # Output: 7 (substring position)
# print(text.index("Java")) # ValueError: substring not found
# Списки
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # Output: 1 (first occurrence)
print(fruits.index("cherry")) # Output: 2
# print(fruits.index("grape")) # ValueError: 'grape' is not in list
# Кортежи
coordinates = (10, 20, 30, 20, 40)
print(coordinates.index(20)) # Output: 1 (first occurrence)
print(coordinates.index(40)) # Output: 4
# У диапазонов нет метода index(), но можно сначала преобразовать
nums = range(10, 50, 5)
nums_list = list(nums)
print(nums_list.index(25)) # Output: 3Если элемент не найден, index() вызывает ValueError. Чтобы избежать этого, сначала проверьте с помощью in:
fruits = ["apple", "banana", "cherry"]
search_fruit = "grape"
if search_fruit in fruits:
position = fruits.index(search_fruit)
print(f"{search_fruit} found at position {position}")
else:
print(f"{search_fruit} not found")
# Output: grape not foundИтерация с циклами for
По любой последовательности можно пройти циклом for:
# Строки — итерация по символам
for char in "Python":
print(char, end=" ")
print() # Output: P y t h o n
# Списки
for fruit in ["apple", "banana", "cherry"]:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherry
# Кортежи
for score in (85, 90, 78):
print(f"Score: {score}")
# Output:
# Score: 85
# Score: 90
# Score: 78
# Диапазоны
for i in range(1, 6):
print(f"Count: {i}")
# Output:
# Count: 1
# Count: 2
# Count: 3
# Count: 4
# Count: 5Операции сравнения
Последовательности можно сравнивать с помощью ==, !=, <, >, <= и >=:
# Равенство
print([1, 2, 3] == [1, 2, 3]) # Output: True
print((1, 2, 3) == (1, 2, 3)) # Output: True
print("abc" == "abc") # Output: True
# Неравенство
print([1, 2, 3] != [1, 2, 4]) # Output: True
print((1, 2) != (1, 2)) # Output: False
# Лексикографическое сравнение (поэлементно)
print([1, 2, 3] < [1, 2, 4]) # Output: True (3 < 4)
print([1, 2, 3] < [1, 3, 0]) # Output: True (2 < 3)
print("apple" < "banana") # Output: True (alphabetical)
print((1, 2) < (1, 2, 3)) # Output: True (shorter is less if equal so far)
# Сравнение разных типов
print([1, 2, 3] == (1, 2, 3)) # Output: False (different types)Сравнение идёт поэлементно слева направо. Первый отличающийся элемент определяет результат.
Понимание этих общих операций позволяет вам писать код, который работает с любым типом последовательности, делая ваши программы более гибкими и переиспользуемыми.
15.9) Продвинутая работа со срезами для всех типов последовательностей
Срезы(slicing) — одна из самых мощных возможностей Python при работе с последовательностями. Хотя базовые срезы мы уже вводили в главе 14, существуют продвинутые техники срезов, которые работают для всех типов последовательностей.
Повторение базовых срезов
Срез извлекает часть последовательности с синтаксисом sequence[start:stop:step]:
# Базовые срезы со строками
text = "Python Programming"
print(text[0:6]) # Output: Python
print(text[7:18]) # Output: Programming
print(text[7:]) # Output: Programming (from index 7 to end)
print(text[:6]) # Output: Python (from start to index 6)
# Базовые срезы со списками
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:7]) # Output: [2, 3, 4, 5, 6]
print(numbers[:5]) # Output: [0, 1, 2, 3, 4]
print(numbers[5:]) # Output: [5, 6, 7, 8, 9]
# Базовые срезы с кортежами
coordinates = (10, 20, 30, 40, 50, 60)
print(coordinates[1:4]) # Output: (20, 30, 40)
print(coordinates[:3]) # Output: (10, 20, 30)
print(coordinates[3:]) # Output: (40, 50, 60)
# Базовые срезы с диапазонами
nums = range(0, 100, 10)
print(list(nums[2:5])) # Output: [20, 30, 40]Помните: start включается, stop не включается, а результат всегда того же типа, что и исходная последовательность.
Использование шага в срезах
Необязательный третий параметр step задаёт, сколько элементов пропускать:
# Каждый второй элемент
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::2]) # Output: [0, 2, 4, 6, 8]
print(numbers[1::2]) # Output: [1, 3, 5, 7, 9]
# Каждый третий элемент
text = "abcdefghijklmnop"
print(text[::3]) # Output: adgjmp
# Шаг со start и stop
print(numbers[2:8:2]) # Output: [2, 4, 6]
print(text[1:10:2]) # Output: bdfhjОтрицательный шаг: разворот последовательностей
Отрицательный шаг разворачивает направление среза:
# Разворот целых последовательностей
text = "Python"
print(text[::-1]) # Output: nohtyP
numbers = [1, 2, 3, 4, 5]
print(numbers[::-1]) # Output: [5, 4, 3, 2, 1]
coordinates = (10, 20, 30, 40)
print(coordinates[::-1]) # Output: (40, 30, 20, 10)
# Разворот с шагом
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[::-2]) # Output: [9, 7, 5, 3, 1] (every second, backwards)
# Разворот части
text = "Python Programming"
print(text[7:18][::-1]) # Output: gnimmargorP (reverse "Programming")При использовании отрицательного шага start и stop работают по-другому:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# При отрицательном шаге start должен быть больше stop
print(numbers[7:2:-1]) # Output: [7, 6, 5, 4, 3] (from 7 down to 3)
print(numbers[8:3:-2]) # Output: [8, 6, 4] (from 8 down to 4, step -2)
# Пропуск start/stop при отрицательном шаге
print(numbers[:5:-1]) # Output: [9, 8, 7, 6] (from end down to 6)
print(numbers[5::-1]) # Output: [5, 4, 3, 2, 1, 0] (from 5 down to start)Отрицательные индексы в срезах
Вы можете использовать отрицательные индексы для позиций start и stop:
text = "Python Programming"
# Последние 11 символов
print(text[-11:]) # Output: Programming
# Всё кроме последних 11 символов
print(text[:-11]) # Output: Python
# От -15 до -5
print(text[-15:-5]) # Output: hon Progra
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Последние 5 элементов
print(numbers[-5:]) # Output: [5, 6, 7, 8, 9]
# Всё кроме последних 3 элементов
print(numbers[:-3]) # Output: [0, 1, 2, 3, 4, 5, 6]
# От -7 до -2
print(numbers[-7:-2]) # Output: [3, 4, 5, 6, 7]Срезы для диапазонов
Срез диапазона возвращает новый объект range:
# Срезы диапазонов
numbers = range(0, 100, 5) # 0, 5, 10, 15, ..., 95
print(numbers) # Output: range(0, 100, 5)
# Срез возвращает новый range
subset = numbers[5:10]
print(subset) # Output: range(25, 50, 5)
print(list(subset)) # Output: [25, 30, 35, 40, 45]
# Со step
every_other = numbers[::2]
print(list(every_other)) # Output: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
# Отрицательный шаг
reversed_range = numbers[::-1]
print(list(reversed_range)) # Output: [95, 90, 85, ..., 5, 0]Пустые срезы и пограничные случаи
numbers = [1, 2, 3, 4, 5]
# Пустые срезы (start >= stop при положительном step)
print(numbers[3:3]) # Output: []
print(numbers[5:10]) # Output: [] (stop beyond length)
print(numbers[10:20]) # Output: [] (both beyond length)
# Срезы за границами последовательности безопасны
print(numbers[-100:100]) # Output: [1, 2, 3, 4, 5] (entire sequence)
print(numbers[2:100]) # Output: [3, 4, 5] (from 2 to end)
# Отрицательный шаг с несовместимыми start/stop
print(numbers[2:7:-1]) # Output: [] (can't go forward with negative step)
# Шаг 0 недопустим
# print(numbers[::0]) # ValueError: slice step cannot be zeroСрезы для копирования
Срез создаёт новую последовательность, что даёт способ копирования:
# Копирование с помощью среза
original = [1, 2, 3, 4, 5]
copy = original[:] # Срез от начала до конца
print(copy) # Output: [1, 2, 3, 4, 5]
# Изменение копии не влияет на оригинал
copy[0] = 100
print(f"Original: {original}") # Output: Original: [1, 2, 3, 4, 5]
print(f"Copy: {copy}") # Output: Copy: [100, 2, 3, 4, 5]
# Это работает и для кортежей (создаёт новый кортеж)
original_tuple = (1, 2, 3, 4, 5)
copy_tuple = original_tuple[:]
print(copy_tuple) # Output: (1, 2, 3, 4, 5)
# Для строк
text = "Python"
text_copy = text[:]
print(text_copy) # Output: PythonОднако помните из главы 14, что это создаёт поверхностную копию(shallow copy).
# Ограничение поверхностной копии
original = [[1, 2], [3, 4]]
copy = original[:]
# Изменение вложенного списка влияет на оба
copy[0][0] = 100
print(f"Original: {original}") # Output: Original: [[100, 2], [3, 4]]
print(f"Copy: {copy}") # Output: Copy: [[100, 2], [3, 4]]Кортежи и диапазоны — важнейшие инструменты в наборе последовательностей Python. Кортежи предоставляют неизменяемые структурированные данные, которые защищают информацию от случайной модификации и позволяют использовать кортежи как ключи словаря. Диапазоны обеспечивают экономное по памяти представление числовых последовательностей — идеально для циклов и больших последовательностей. Понимание того, когда использовать каждый тип — и как преобразовывать типы друг в друга — делает ваш код более эффективным, безопасным и более ясно выражающим намерения.
Общие операции, разделяемые всеми типами последовательностей — индексация, срезы, итерация, проверка принадлежности — формируют единый интерфейс, который делает работу с любой последовательностью интуитивной. Продвинутые техники срезов дают вам мощные выразительные способы извлекать и преобразовывать данные последовательностей.
По мере того как вы продолжаете программировать на Python, вы обнаружите, что естественным образом выбираете правильный тип последовательности для каждой ситуации: списки — для коллекций, которые меняются, кортежи — для фиксированных записей, диапазоны — для числовых последовательностей, а строки — для текста. Эта глава дала вам знания, чтобы уверенно делать такой выбор и эффективно использовать каждый тип.