Solution 1

class OperationInvalidException(Exception):
    pass


class Operation(object):
    def __init__(self, *args):
        self.args = args

    def operate(self):
        raise NotImplementedError()


class AddOperation(Operation):

    def operate(self):
        return sum(self.args)


class SubtractOperation(Operation):

    def operate(self):
        if not self.args:
            return 0
        return self.args[0] - sum(self.args[1:])


class Calculator(object):

    def __init__(self, operations):
        self.operations = operations

    def calculate(self, *args):
        numbers, operation = args[:-1], args[-1]
        if operation not in self.operations:
            raise OperationInvalidException
        operation = self.operations[operation](*numbers)
        return operation.operate()

You'll need to build a better version of your calculator using OOP and inheritance:
A calculator can be built with different operations. An Operation is an abstract class for which you'll define
subclasses.

Example:

calc1 = Calculator(operations={
    'add': AddOperation,
    'subtract': SubtractOperation})

calc2 = Calculator(operations={
    'add': AddOperation})

The calculator has 1 generic method calculate that will receive the arguments
and the operation to perform. For example:

calc1.calculate(2, 1, 5, 'add')  # Should return 2 + 1 + 5 = 8
calc2.calculate(1, 5, 'add')  # Should return 1 + 5 = 6

IMPORTANT: The number of arguments should be variable

The Calculator will check if it has that computation present and
invoke the operation. Operations are initialized with the arguments to compute:

op = AddOperation(2, 1, 5)

Once you have an operation object created you should be able to invoke the operate
method PRESENT IN EVERY OPERATION.

op.operate()  # Should return 8

Important notes:
The only method that you must implement for every operation (descendant from Operation) is the operate method.
If the operation is not supported by the calculator (for example invoking multiply on calc1) the calculator should raise a custom exception (built by you) named OperationInvalidException.

Test Cases

test calculator with one operation - Run Test

def test_calculator_with_one_operation():
    calc = Calculator(
        operations={
            'add': AddOperation
        }
    )

    assert calc.calculate(1, 5, 13, 2, 'add') == 21

test calculator with multiple operations - Run Test

def test_calculator_with_multiple_operations():
    calc = Calculator(
        operations={
            'add': AddOperation,
            'subtract': SubtractOperation
        }
    )
    assert calc.calculate(1, 5, 13, 2, 'add') == 21
    assert calc.calculate(13, 3, 7, 'subtract') == 3

test subtract operation - Run Test

def test_subtract_operation():
    op = SubtractOperation(10, 1, 3, 2, 1)
    assert op.operate() == 3

test calculator invoked with an invalid operation - Run Test

import pytest

def test_calculator_invoked_with_an_invalid_operation():
    calc = Calculator(
        operations={
            'add': AddOperation
        }
    )
    with pytest.raises(OperationInvalidException, message='Expecting OperationInvalidException'):
        res = calc.calculate(1, 5, 13, 2, 'INVALID')

test add operation - Run Test

def test_add_operation():
    op = AddOperation(5, 1, 8, 3, 2)
    assert op.operate() == 19
# Hint: don't forget the custom exception class Operation(object): def __init__(self, *args): # Do something here pass def operate(self): raise NotImplementedError() class AddOperation(Operation): # The only method present in this class def operate(self): pass class SubtractOperation(Operation): def operate(self): pass class Calculator(object): pass