Easy📖Теория5 min

Списки и кортежи

Создание, индексация, срезы, методы списков, list как стек, кортежи и namedtuple

Списки и кортежи

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

Списки (list)

Список -- упорядоченная изменяемая коллекция произвольных объектов:

# Creating lists
empty = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None]
nested = [[1, 2], [3, 4], [5, 6]]

# From other iterables
from_range = list(range(10))         # [0, 1, 2, ..., 9]
from_string = list("Python")        # ['P', 'y', 't', 'h', 'o', 'n']
from_tuple = list((1, 2, 3))        # [1, 2, 3]

# Repeat elements
zeros = [0] * 5                      # [0, 0, 0, 0, 0]

# WARNING: mutable objects repeat the SAME reference
matrix = [[0] * 3] * 3  # WRONG! All rows are the same object
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] - all rows changed!

# Correct way
matrix = [[0] * 3 for _ in range(3)]  # Each row is independent
matrix[0][0] = 1
print(matrix)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

Индексация и срезы

fruits = ["яблоко", "банан", "вишня", "дыня", "ежевика"]

# Indexing
print(fruits[0])     # 'яблоко'
print(fruits[-1])    # 'ежевика'
print(fruits[-2])    # 'дыня'

# Slicing: list[start:stop:step]
print(fruits[1:3])    # ['банан', 'вишня']
print(fruits[:3])     # ['яблоко', 'банан', 'вишня']
print(fruits[2:])     # ['вишня', 'дыня', 'ежевика']
print(fruits[::2])    # ['яблоко', 'вишня', 'ежевика']
print(fruits[::-1])   # reversed list

# Slice assignment (modify multiple elements)
numbers = [0, 1, 2, 3, 4, 5]
numbers[1:4] = [10, 20]     # replace 3 elements with 2
print(numbers)               # [0, 10, 20, 4, 5]

numbers[2:2] = [99, 98]     # insert without removing
print(numbers)               # [0, 10, 99, 98, 20, 4, 5]

# Delete via slice
numbers[1:3] = []            # remove elements
print(numbers)               # [0, 98, 20, 4, 5]

Методы списков

fruits = ["яблоко", "банан"]

# Adding elements
fruits.append("вишня")           # Add to end: ['яблоко', 'банан', 'вишня']
fruits.insert(1, "абрикос")      # Insert at index: ['яблоко', 'абрикос', 'банан', 'вишня']
fruits.extend(["дыня", "ежевика"]) # Add multiple elements

# Removing elements
fruits.remove("банан")            # Remove first occurrence (ValueError if not found)
last = fruits.pop()               # Remove and return last element
second = fruits.pop(1)            # Remove and return element at index
fruits.clear()                    # Remove all elements

# Finding elements
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(numbers.index(4))           # 2 (first index of value)
print(numbers.index(1, 2))        # 3 (search from index 2)
print(numbers.count(1))           # 2 (number of occurrences)

# Sorting
numbers.sort()                     # Sort in place: [1, 1, 2, 3, 4, 5, 6, 9]
numbers.sort(reverse=True)         # Descending: [9, 6, 5, 4, 3, 2, 1, 1]

# Sort by key function
words = ["banana", "apple", "cherry", "date"]
words.sort(key=len)                # Sort by length: ['date', 'apple', 'banana', 'cherry']
words.sort(key=str.lower)          # Case-insensitive sort

# sorted() creates a new list (does not modify original)
original = [3, 1, 4, 1, 5]
new_sorted = sorted(original)
print(original)     # [3, 1, 4, 1, 5] (unchanged)
print(new_sorted)   # [1, 1, 3, 4, 5]

# Reversing
numbers = [1, 2, 3, 4, 5]
numbers.reverse()                  # In place: [5, 4, 3, 2, 1]
reversed_new = list(reversed(numbers))  # New list

# Copy
original = [1, [2, 3], 4]
shallow = original.copy()          # Shallow copy (same as original[:])
shallow[1][0] = 99                 # Modifies nested list in both!
print(original)                    # [1, [99, 3], 4]

import copy
deep = copy.deepcopy(original)    # Deep copy (fully independent)
deep[1][0] = 0
print(original)                    # [1, [99, 3], 4] (unchanged)

Список как стек (LIFO)

# Stack: Last In, First Out
stack = []

# Push
stack.append("первый")
stack.append("второй")
stack.append("третий")
print(stack)  # ['первый', 'второй', 'третий']

# Pop
top = stack.pop()
print(top)    # 'третий'
print(stack)  # ['первый', 'второй']

# Peek (look at top without removing)
if stack:
    print(stack[-1])  # 'второй'

Кортежи (tuple)

Кортеж -- упорядоченная неизменяемая последовательность:

# Creating tuples
empty = ()
single = (42,)              # COMMA is required for single-element tuple!
not_a_tuple = (42)          # This is just int 42 with parentheses
pair = (1, 2)
coords = 3.14, 2.71        # Parentheses are optional
mixed = (1, "hello", True)

# From other iterables
from_list = tuple([1, 2, 3])
from_range = tuple(range(5))

# Indexing and slicing (same as lists)
point = (10, 20, 30)
print(point[0])      # 10
print(point[-1])     # 30
print(point[1:])     # (20, 30)

# Tuples are IMMUTABLE
# point[0] = 99       # TypeError!

# But mutable elements inside can change!
data = ([1, 2], [3, 4])
data[0].append(99)
print(data)          # ([1, 2, 99], [3, 4])

Распаковка кортежей

# Unpacking
x, y, z = (10, 20, 30)
print(x, y, z)  # 10 20 30

# Star unpacking
first, *rest = (1, 2, 3, 4, 5)
print(first)   # 1
print(rest)    # [2, 3, 4, 5]

*start, last = (1, 2, 3, 4, 5)
print(start)   # [1, 2, 3, 4]
print(last)    # 5

first, *middle, last = (1, 2, 3, 4, 5)
print(middle)  # [2, 3, 4]

# Nested unpacking
data = ("Иван", (25, "Москва"))
name, (age, city) = data
print(f"{name}, {age}, {city}")  # Иван, 25, Москва

# Ignore values with _
_, y, _ = (10, 20, 30)
print(y)  # 20

# Swap values (tuple unpacking)
a, b = 1, 2
a, b = b, a
print(a, b)  # 2 1

Кортежи vs списки

import sys

# Memory: tuples use less memory
lst = [1, 2, 3, 4, 5]
tpl = (1, 2, 3, 4, 5)
print(sys.getsizeof(lst))  # ~96 bytes
print(sys.getsizeof(tpl))  # ~80 bytes

# Performance: tuples are slightly faster
import timeit
print(timeit.timeit('(1,2,3,4,5)', number=1_000_000))  # ~0.01s
print(timeit.timeit('[1,2,3,4,5]', number=1_000_000))  # ~0.05s

# Tuples can be dict keys and set members (hashable)
locations = {(55.75, 37.62): "Москва", (59.93, 30.32): "Петербург"}

# Lists cannot be keys
# {[1, 2]: "value"}  # TypeError: unhashable type: 'list'

# When to use which:
# Tuple: fixed structure, return multiple values, dict keys, immutable data
# List: dynamic collection, need to add/remove, order may change

NamedTuple

NamedTuple -- кортеж с именованными полями:

from typing import NamedTuple

# Modern syntax with class (preferred)
class Point(NamedTuple):
    x: float
    y: float
    z: float = 0.0  # default value

p = Point(1.0, 2.0, 3.0)
print(p.x)        # 1.0
print(p[0])       # 1.0 (still works like a tuple)
print(p)           # Point(x=1.0, y=2.0, z=3.0)

# Unpacking works
x, y, z = p
print(f"({x}, {y}, {z})")

# Immutable
# p.x = 10  # AttributeError!

# _replace creates a new instance
p2 = p._replace(x=10.0)
print(p2)  # Point(x=10.0, y=2.0, z=3.0)

# Convert to dict
print(p._asdict())  # {'x': 1.0, 'y': 2.0, 'z': 3.0}

# Practical example
class HTTPResponse(NamedTuple):
    status: int
    body: str
    headers: dict[str, str] = {}

response = HTTPResponse(200, '{"ok": true}', {"Content-Type": "application/json"})
print(f"Status: {response.status}")
print(f"Body: {response.body}")

# Old syntax (still valid but less readable)
from collections import namedtuple
Color = namedtuple("Color", ["red", "green", "blue"])
red = Color(255, 0, 0)

Использование NamedTuple для возврата нескольких значений

from typing import NamedTuple

class Stats(NamedTuple):
    mean: float
    median: float
    mode: float
    std_dev: float

def calculate_stats(data: list[float]) -> Stats:
    """Calculate basic statistics for a dataset."""
    import statistics
    return Stats(
        mean=statistics.mean(data),
        median=statistics.median(data),
        mode=statistics.mode(data),
        std_dev=statistics.stdev(data),
    )

data = [1, 2, 2, 3, 4, 4, 4, 5]
stats = calculate_stats(data)
print(f"Среднее: {stats.mean:.2f}")
print(f"Медиана: {stats.median}")
print(f"Мода: {stats.mode}")
print(f"СКО: {stats.std_dev:.2f}")

Полезные функции для последовательностей

numbers = [3, 1, 4, 1, 5, 9, 2, 6]

# Built-in functions
print(len(numbers))     # 8
print(min(numbers))     # 1
print(max(numbers))     # 9
print(sum(numbers))     # 31
print(sorted(numbers))  # [1, 1, 2, 3, 4, 5, 6, 9]
print(any(x > 8 for x in numbers))  # True
print(all(x > 0 for x in numbers))  # True

# Membership testing
print(5 in numbers)     # True
print(10 in numbers)    # False

# Enumerate for indexed iteration
for i, num in enumerate(numbers, start=1):
    print(f"#{i}: {num}")

# Zip for parallel iteration
names = ["Иван", "Мария"]
ages = [25, 30]
for name, age in zip(names, ages):
    print(f"{name}: {age}")

Итоги

  • Список -- изменяемая последовательность, основная рабочая лошадка
  • Кортеж -- неизменяемая последовательность, легче и быстрее
  • [[0]*3]*3 -- ловушка с общими ссылками; используйте comprehension
  • list.sort() сортирует на месте, sorted() создаёт новую последовательность
  • copy.deepcopy() для полного копирования вложенных структур
  • NamedTuple -- кортежи с именованными полями и типами
  • Кортежи можно использовать как ключи словарей (hashable)
  • Распаковка * позволяет гибко извлекать элементы

Проверь себя

🧪

Что произойдёт при выполнении: matrix = [[0]*3]*3; matrix[0][0] = 1?

🧪

Какой метод списка добавляет все элементы из другого итерируемого объекта?

🧪

Как создать кортеж из одного элемента?

🧪

Чем NamedTuple лучше обычного кортежа?