заранее извиняюсь, всё нижеперечисленное -- исключительно моё имхо.
но так, навскидку:
1) открой для себя try...except.
все эти "Вы ввели не правильные данные! Попробуйте снова." внутри функций хороши лишь для одной задачи -- для этой. если ты решишь сдёрнуть, скажем, функцию сложения матриц в другой свой проект, эти print-ы "Размерность матриц не одинакова! Попробуйте снова." начнут мешать.
лучше, когда функция делает ровно то, для чего предназначена. или не делает -- если входные данные ей не по душе. поэтому:
2) открой для себя assert-ы.
функции всё-таки чаще общаются с вызывающими функциями -- через коды возврата, через исключения и т.п.
взаимодействие же с пользователем пусть осуществляют функции, которые ты создаёшь специально для этого самого взаимодействия. отсюда:
3) пообщались с пользователем -> вызвали нужную ему функцию -> результат работы функции проинтерпретировали пользователю -> пообщались с пользователем -> и т.п.
кроме того, пользователь может задолбаться и захотеть выйти в любой момент. надо дать ему возможность сделать это легально, а не через аварийный останов. так что:
"Вы ввели неправильные данные! Попробуйте снова или нажмите Enter для завершения работы."
кстати, проверка user_input[index] != "-" недостаточна для счастья.
кто-нибудь может попробовать ввести "12-34" или "+56" или даже "1e+10".
4) также для читабельности кода полезны подсказки типов данных:
для старых питонов:
from typing import List
Matrix = List[List[float]]
для молодых питонов:
type Matrix = list[list[float]]
применять примерно так:
def matrix_sub (param1 : Matrix, param2 : Matrix) -> Matrix :
....
def matrix_transpose (m : Matrix) -> Matrix :
....
5) ну и постфикс ..._func цепляет глаз. обычно, если видишь в коде matrix_func, ожидаешь функцию, а оно, оказывается, параметр...
Cсылка на репозиторий: Проект