2025, Oct 31 16:46
Консольный вывод в Python без мусора и дублей: аккуратное форматирование арифметики
Почему в консоли Python появляются лишние скобки, запятые и дубли результата. Покажем причину и дадим исправленный код с f-строками и корректным выравниванием
Когда вы оформляете вывод в консоли вокруг арифметических операций, малейшая неточность может испортить результат. В нашем случае «научный» калькулятор печатает лишние запятые и скобки, а итог выводится дважды. Причина не в математике — она в том, как при верстке обрабатываются строки и итерируемые объекты.
Как воспроизвести проблему
Ниже приведен фрагмент, который точно воспроизводит поведение: посторонние символы посреди вывода и дублированная строка с результатом.
class CalcPretty:
def __init__(self, *vals):
self.vals = vals
def render(self, nums, op, outcome):
w = max(len(str(x)) for x in (list(nums) + list(outcome))) + 2
rows = []
rows.append(f"{str(nums[0]).rjust(w)}")
for ch in str(nums[1:]):
rows.append(f"{op}{ch.rjust(w-1)}")
dashes = '-' * w
rows.append(dashes)
rows.append(f"{str(outcome).rjust(w)}")
return "\n".join(rows)
def sumup(self, *args):
total = sum(self.vals)
s_total = str(total)
print(self.render(self.vals, '+', s_total))
return s_total
obj = CalcPretty(23, 2, 3)
print(obj.sumup())
Что идет не так и почему
Сбой происходит в двух местах. Во‑первых, выражение, которое раскладывает последующие значения, преобразует срез кортежа в строку. Вызов str(nums[1:]) возвращает буквальное представление кортежа, например «(2, 3)», а итерация по этой строке выдает по одному символу — включая скобки, запятую и пробел. Поэтому «(», «,» и «)» оказываются на отдельных строках, еще и с подставленным оператором.
Дублирование суммы связано со смешением обязанностей: метод и печатает оформленный блок, и возвращает значение, а вызывающий код затем снова печатает это значение. Печать внутри и снаружи дает две строки с результатом.
Есть и нюанс проектирования. Чище передавать числа в метод операции, а не хранить их в объекте. Так поток данных очевиднее и не возникает «залежалого» состояния. И, наконец, код верстки не должен предполагать, что оператор — это один символ; учет его реальной длины делает выравнивание устойчивым.
Исправляем форматирование и логику
Исправленный вариант форматирует только сами числа, учитывает ширину оператора и возвращает уже собранный блок — печатать его решает вызывающий код. f-строки делают выравнивание одновременно наглядным и лаконичным.
class CalcOps:
def layout_block(self, seq: tuple[int|float, ...], op: str, out_str: str) -> str:
opw = len(op)
span = max(len(str(v)) for v in (list(seq) + [out_str])) + opw + 1
lines = [f"{seq[0]:>{span}}"]
for v in seq[1:]:
lines.append(f"{op}{v:>{span - opw}}")
lines.append('-' * span)
lines.append(f"{out_str:>{span}}")
return "\n".join(lines)
def plus(self, *vals: int|float) -> str:
res = str(sum(vals))
return self.layout_block(vals, "+", res)
print(CalcOps().plus(23, 2, 3))
Вывод:
23
+ 2
+ 3
----
28
Почему это важно для производственного кода
Преобразование контейнеров — кортежей или списков — в строку с последующей итерацией по ней — скрытая ловушка: вы перебираете символы, а не значения. В оформленном консольном или логовом выводе это приводит к вводящим в заблуждение символам, которые выглядят как ошибки логики где‑то выше по стеку. Также окупается разделение вычислений и представления: верните оформленный блок и позвольте вызывающему коду печатать его — так не будет дубликатов и тестировать станет проще.
Итоги
Итерируйтесь по реальным значениям, а не по строковым представлениям контейнеров, если хотите аккуратное числовое выравнивание. Вычисляйте ширину по фактическим длинам, включая сам оператор, чтобы форматирование не ломалось при его смене. Печать держите на «краях»: метод операции возвращает строку, а печать происходит в месте вызова. Эти небольшие привычки убирают шум в выводе и делают оформление арифметики в CLI‑инструментах предсказуемым и поддерживаемым.