2025, Oct 31 12:47
Как транспонировать матрицу на месте в Python без импортов
Разбираем, почему переназначение не меняет исходный список, и показываем способы транспонирования матрицы на месте в Python: clear(), срезы и zip без импортов.
Транспонировать матрицу «на месте» в Python непросто на практике. Ключевая идея в том, что повторное присваивание локальной переменной внутри функции не меняет объект у вызывающей стороны, тогда как мутация объекта — меняет. Если вы создаёте транспонированную копию и присваиваете её имени параметра внутри функции, исходная матрица снаружи останется неизменной. Ниже — разбор этой ситуации и несколько аккуратных способов выполнить транспонирование на месте без импортов.
Постановка задачи
Цель — транспонировать матрицу так, чтобы изменилась исходная переменная. В примере ниже внутри функции печатается ожидаемый результат, но снаружи матрица остаётся прежней.
def flip_grid(grid: list):
    alt = []
    for i in range(len(grid)):
        row = []
        for j in range(len(grid)):
            row.append(grid[j][i])
        alt.append(row)
    grid = []
    for i in range(len(alt)):
        row = []
        for j in range(len(alt)):
            row.append(alt[i][j])
        grid.append(row)
    print(grid)  # внутри функции
board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flip_grid(board)
print(board)     # снаружи функции
Что происходит и почему
Имя, которое вы передаёте в функцию, связывается с локальным параметром. Внутри функции присваивание grid = [] не мутирует исходный список; оно лишь переназначает локальное имя grid на новый пустой список. У вызывающей стороны остаётся ссылка на старый список. Чтобы изменить исходную матрицу, нужно мутировать сам объект списка, переданный в функцию, а не переназначать локальную переменную.
Исправление с минимальными изменениями
Чтобы действительно модифицировать тот же объект списка, очистите его или используйте присваивание срезу, а затем заполните. Замените локальное переназначение на операцию мутации.
def flip_grid(grid: list):
    alt = []
    for i in range(len(grid)):
        row = []
        for j in range(len(grid)):
            row.append(grid[j][i])
        alt.append(row)
    # изменяем, а не переназначаем
    grid.clear()          # или: grid[:] = []
    for i in range(len(alt)):
        row = []
        for j in range(len(alt)):
            row.append(alt[i][j])
        grid.append(row)
board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flip_grid(board)
print(board)
Так сохраняется идентичность объекта списка, на который ссылается вызывающий код, а его содержимое обновляется транспонированными данными.
Короткий вариант «на месте»
Можно обойтись без временной копии: вычислите транспонирование напрямую и замените содержимое через присваивание срезу. Вот два компактных решения.
def transpose_in_place(data: list):
    data[:] = [
        [row[col_idx] for row in data]
        for col_idx in range(len(data[0]))
    ]
Ещё один лаконичный вариант даёт тот же эффект:
def transpose_in_place(data: list):
    data[:] = [list(col) for col in zip(*data)]
Почему это важно
Различие между переназначением имени и мутацией объекта — фундаментально в Python. От него зависит, как вы проектируете API, учитываете побочные эффекты и отлаживаете функции, которые должны изменять свои входные данные. Если ваша цель — обновить данные вызывающей стороны, мутируйте объект на месте; если цель — вычислить новое значение, верните его и позвольте вызывающему коду переназначить переменную. В ряде случаев использовать специализированную библиотеку разумнее, но если вы избегаете импортов в учебных целях, показанные приёмы «на месте» остаются ясными и эффективными.
Вывод
Чтобы изменить исходную матрицу из функции, не переназначайте имя параметра. Мутируйте сам список — например, очистите его или используйте присваивание срезу, — а затем заполните транспонированными строками. Если ближе функциональный подход, верните новую матрицу и переназначьте её в месте вызова. В любом случае явное разделение мутации и присваивания сделает работу с матрицами предсказуемой и корректной.