Сайт вебмастера

Простой калькулятор на Python

28-02-2023Время чтения ~ 7 мин.Python 1219

Популярная задача для новичков Python — написание простейшего калькулятора. Алгоритм будет состоять из двух частей: первая — это ввод данных (арифметическое выражение) и второй — это его выполнение. Мы рассматриваем консольное приложение.

Существует как минимум три принципиально разных подхода к созданию такой программы. Первый — по сути хак — использование для интерпретации выражения сам Python. Для этого используется встроенная функция eval(), которая интерпретирует переданную строку как обычный Python-код. Калькулятор в таком случае может быть написан в одну строчку.

print('Результат:', eval(input('Введите арифметическое выражение: ')))

С помощью input() мы принимаем произвольное выражение, которое передаётся напрямую в eval(). Результат выводим с помощью обычного print().

Такой калькулятор будет достаточно крутой, поскольку здесь используется вся мощь Python. Например здесь можно использовать любые операции, а также скобки, функции и даже локальные переменные.

Такую программу можно немного доработать, чтобы сделать её более интеллектуальной. Добавим бесконечный цикл, чтобы иметь возможность повторно что-либо вычислить, а также добавим обработку исключений.

while True:
    # Запрашиваем у пользователя ввод арифметического выражения
    expression = input('Введите арифметическое выражение (или Enter для выхода): ')
    
    # Выходим из цикла, если пользователь ввел пустую строку
    if not expression:
        break

    try:
        # Используем функцию eval для вычисления выражения
        result = eval(expression)
        print('Результат:', result)
    except ZeroDivisionError:
        print('Ошибка: деление на ноль')
    except Exception as e:
        print('Ошибка:', e)

Если бы задача действительно стояла в получении калькулятора, то можно было бы вообще ничего не писать, а просто запустить оболочку IDLE или в консоли набрать:

python -i

Суть та же — интерпретация входной строки будет выполнять Python. Поэтому для учебного проекта вариант с eval() не годится.

Преподаватель вообще смотрит даже не на то как работает программа, а на то, как вы подходите к её решению.

Поэтому задача стоит так, чтобы арифметические вычисления были произведены непосредственной программой.

Сложность задачи в том, чтобы научить программу разделять входящую строку. Для новичков это может оказаться достаточно сложным, поэтому есть смысл упростить эту часть: отдельно вводить два числа и операцию с ними.

print('Выберите операцию.')
print('1. Сложение')
print('2. Вычитание')
print('3. Умножение')
print('4. Деление')
print('-------------------')

# Запрос ввода пользователем
choice = input('Введите номер операции (1/2/3/4): ')

num1 = float(input('Введите первое число: '))
num2 = float(input('Введите второе число: '))

if choice == '1': # Сложение
    print(num1, '+', num2,'=', num1+num2)

elif choice == '2': # Вычитание
    print(num1, '-', num2,'=', num1-num2)

elif choice == '3': # Умножение
    print(num1, '*', num2, '=', num1*num2)

elif choice == '4': # Деление
    print(num1, '/', num2, '=', num1/num2)
else:
    print('Неправильный ввод :-(')

input('Нажмите Enter для выхода...')

Алгоритм достаточно простой: вначале выводим подсказку и просим пользователя выбрать операцию. После этого просим ввести первое и второе число, которые сразу же преобразуем в тип float. Само же вычисление происходит при выводе в print().

Если вы хотите показать, что умеете создавать функции, то можно вынести в них математические операции.

Теперь давайте усложним задачу. Пусть пользователь всё-таки вводит данные в более-менее привычном формате. Например: «2+2», «4*3» и т.п. Такую строку следует разделить на части, где разделитель будет один из символов: «+-*/». То есть у нас должен получиться операнд1, оператор и операнд2. После этого мы можем проанализировать оператор и выполнить нужное действие.

Для непосредственного выполнения арифметики мы можем написать отдельные функции, но можно воспользоваться стандартной библиотекой Python operator. Это набор функций, которые принимают несколько аргументов.

В Python вообще принята практика использования уже существующих библиотек, особенно если они стандартные. То есть вместо того, чтобы изобретать велосипед, поощряется использование существующих разработок.

Для разбора входной строки будем использовать простое регулярное выражение с помощью стандартного модуля re.

Последовательно рассмотрим создание такой программы.

Вначале определимся с общим каркасом приложения. Посмотрите его внимательно.

# подключаем модули
import re
import operator

# базовые переменные
...

while True:
    # Запрашиваем у пользователя ввод арифметического выражения
    expression = input('Введите арифметическое выражение (или Enter для выхода): ')
    
    # Выходим из цикла, если пользователь ввел пустую строку
    if not expression:
        break

    try:
        # Убираем пробелы из выражения
        ...

        # Разбиваем выражение на операнды и операторы на основе словаря ops
        ...
        
        # Вызываем соответствующую функцию из operator, получаем результат и выводим его
        ...
    
    # отлавливаем ошибки
    except IndexError:
        print('Ошибка: неправильный формат выражения')
    except ZeroDivisionError:
        print('Ошибка: деление на ноль')
    except Exception as e:
        print('Ошибка:', e)

В переменной expression у нас изначально строка от пользователя. Мы предполагаем, что пользователь может использовать пробелы: «2+3», или «2 + 3», или «2+ 3» — любые варианты, поэтому из выражения нужно удалить все пробелы. Поскольку expression у нас обычная строка, то будем использовать её метод replace():

expression = expression.replace(' ', '')

Теперь нам нужно получить список из элементов этой строки. Мы знаем, что разделителем может быть символ операции, поэтому будем использовать простое регулярное выражение:

operands = re.split(r'([-+*/])', expression)

Метод split() принимает паттерн по которому нужно разделить строку. На выходе будет список найденных групп. Рассмотрим паттерн.

Первое на что нужно обратить внимание — это raw-строка — первый символ r. Для регулярки это важно, поскольку в Python строки могут интерполироваться.

Паттерн задаётся в виде строки. Мы указываем группу с помощью круглых скобок поскольку без них split() будет возвращать только группы без разделителя. У нас разделители задаются в виде группы символов в квадратных скобках. Таким образом split() будет искать любой символ из квадратных скобок, после этого формировать часть строки до разделителя, потом сам разделитель и текст после разделителя.

Вот вам два примера для тестирования:

print(re.split(r'([-+*/])', '2+3')) # ['2', '+', '3']
print(re.split(r'[-+*/]', '2+3')) # ['2', '3']

Это особенность работы split() — использование круглых скобок вернет в массиве и сам разделитель. Для нас это очень удобно, поскольку мы можем напрямую обращаться к элементам списка по индексам. Нулевой элемент — это первое число, первый — операция, второй — второе число.

operand1 = float(operands[0])
operand2 = float(operands[2])

Теперь у нас есть все входящие данные и нужно передать их в методы operator. Нас интересует cамое простое:

  • сложение (+) - operator.add
  • вычитание (-) - operator.sub
  • умножение (*) - operator.mul
  • деление (/) - operator.truediv

Мы можем пойти на хитрость и оформить словарь, который будет хранить символ операции для ключа, и соответствующую функцию operator.

ops = {
    '+': operator.add, 
    '-': operator.sub, 
    '*': operator.mul, 
    '/': operator.truediv
}

Таким образом получить нужную функцию можно просто обратившись к этому словарю:

func = ops[operands[1]]

Соответственно мы можем её вызвать:

result = func(operand1, operand2)

Здесь важно понять, что функцию мы получили по ссылке, а выполнили, когда вызвали её с помощью круглых скобок. Так устроен Python, что все объекты передаются по ссылкам. Поэтому, когда мы формируем ops, то на самом деле в нём оказываются ссылки на нужные методы. Обратите внимание, что у них нет круглых скобок, то есть мы их не вызываем, а просто получаем ссылку. Если вы уже знаете, что такое декораторы, то они базируются на этом же принципе.

Поэтому в итоге мы можем получить в переменной func ссылку на нужный нам метод без его выполнения.

Теперь итоговый код:

import re # https://docs.python.org/3/library/re.html
import operator # https://docs.python.org/3/library/operator.html

# Определяем словарь для связывания операторов с соответствующими функциями из библиотеки operator
ops = {
    '+': operator.add, 
    '-': operator.sub, 
    '*': operator.mul, 
    '/': operator.truediv
}

while True:
    # Запрашиваем у пользователя ввод арифметического выражения
    expression = input('Введите арифметическое выражение (или Enter для выхода): ')
    
    # Выходим из цикла, если пользователь ввел пустую строку
    if not expression:
        break

    try:
        # Убираем пробелы из выражения
        expression = expression.replace(' ', '')

        # Разбиваем выражение на операнды и операторы на основе словаря ops
        operands = re.split(r'([-+*/])', expression)
 
        operand1 = float(operands[0])
        operand2 = float(operands[2])
        func = ops[operands[1]]
        
        # Вызываем соответствующую функцию из словаря по оператору и передаем ей операнды в качестве аргументов
        result = func(operand1, operand2)
        print('Результат:', result)
    except IndexError:
        print('Ошибка: неправильный формат выражения')
    except ZeroDivisionError:
        print('Ошибка: деление на ноль')
    except Exception as e:
        print('Ошибка:', e)

Если вы хотите немного упростить программу, то попробуйте её переделать так, чтобы отказаться от модуля operator. Для этого придётся либо написать свои арифметические функции, либо прямо в цикле выполнять арифметику в виде условий. Примерно так:

simbol = operands[1] # символ операции

if simbol == '+':
    result = operand1 + operand2
elif simbol == '-':
    result = operand1 - operand2
... # и т.д.

Если мы хотим отказаться от регулярных выражений, то нужно вспомнить, что у строк есть метод split(), который разделяет строку по заданному символу. Чтобы его использовать потребуется последовательно проверить вхождение символа «+» — если он есть, то разбиваем строку и получаем числа для сложения. Если символа «+» нет, то проверяем следующий «-» и т.д.

Похожие записи