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‑инструментах предсказуемым и поддерживаемым.

Статья основана на вопросе на StackOverflow от new_coder и ответе Ramrab.