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, учитываете побочные эффекты и отлаживаете функции, которые должны изменять свои входные данные. Если ваша цель — обновить данные вызывающей стороны, мутируйте объект на месте; если цель — вычислить новое значение, верните его и позвольте вызывающему коду переназначить переменную. В ряде случаев использовать специализированную библиотеку разумнее, но если вы избегаете импортов в учебных целях, показанные приёмы «на месте» остаются ясными и эффективными.
Вывод
Чтобы изменить исходную матрицу из функции, не переназначайте имя параметра. Мутируйте сам список — например, очистите его или используйте присваивание срезу, — а затем заполните транспонированными строками. Если ближе функциональный подход, верните новую матрицу и переназначьте её в месте вызова. В любом случае явное разделение мутации и присваивания сделает работу с матрицами предсказуемой и корректной.