Is there a way to input a list into a function which has *args as its argument in Python 3.6?

Viewed 60

Currently, I'm working on a problem on CodeWars named "Training on Calculating with Functions" using Python 3.6. The way that this problem works is, for example, if seven(times(five())) is called, then the function should return 35. Here is a snippet from my code:

def nine(arr):  # your code here
    if arr is None:
        return 9
    return operator(arr[0], 9, arr[1])


def plus(num):  # your code here
    return ["+", num]


def operator(op, n1, n2):
    switcher = {
        "+": n1 + n2,
        "-": n1 - n2,
        "*": n1 * n2,
        "/": n1 / n2
    }

    return switcher.get(op, "Invalid")

The full code is on this link (with -> top and without -> bottom args): Hastebin

My logic here (originally) was to use *args as the argument, and if args was None then the function would return its own value (five() => 5), this value would then be passed into one of the 'operator' functions, which would return a list of its own operator (i.e. +, -, /, *) and the number passed into it. Then, in the outermost function, you would pass in the array from the 'operator' function, since args was not None, you would take the first item in the args tuple (the returned value from the 'operator' function which was passed in), then you would access that lists' first and second values, the rest is pretty self-explanatory.

However, when I attempted to run this, I would get the error index beyond tuple range (ref: line 95 on Hastebin). As a result, I switched to a new logic where I would explicitly add the argument to. the function, but (obviously), when run, there was an error that no argument was passed into the function (innermost function --> five() in seven(times(five()))).

I would like to know if there are any ways I can fix my existing code, but any alternate solutions are more than welcome.

Additionally, here is a link to the kata: Codewars Kata

1 Answers

You can give a default value to the argument. If no argument is passed, the default value will be used instead. Since you check for None, you can use None as the default value.

def nine(arr=None):
    if arr is None:
        return 9
    else:
        return operator(arr[0], 9, arr[1])

def five(arr=None):
    if arr is None:
        return 5
    else:
        return operator(arr[0], 5, arr[1])

def plus(num):  # your code here
    return ["+", num]

def operator(op, n1, n2):
    switcher = {
        "+": n1 + n2,
        "-": n1 - n2,
        "*": n1 * n2,
        "/": n1 / n2
    }
    return switcher.get(op, "Invalid")

print(nine(plus(five()))
# 14

Issue with your operator function: python dict is non-lazy

Note that python dict are not lazy. This means that when you define switcher the way you do, it immediately performs the four calculations n1 + n2, n1 - n2, n1 * n2, n1 / n2. This is a problem, especially since the division n1 / n2 will fail with exception ZeroDivisionError if n2 is 0.

One solution is to use a dict of functions instead of a dict of numbers:

def operator(op, n1, n2):
    switcher = {
        "+": lambda x,y: x+y,
        "-": lambda x,y: x-y,
        "*": lambda x,y: x*y,
        "/": lambda x,y: x/y,
    }
    return switcher[op](n1, n2)

Note that those four functions are already defined in the python module operator and are called add, sub, mul, floordiv (or truediv). Refer to the documentation on module operator. For instance, operator.add(x,y) is synonymous for x+y. But you can write fun things like return operator.add, whereas you can't write return (+) in python.

Since this module is already called operator, you should probably find another name for your operator function.

import operator

def nine(arr=None):
    if arr is None:
        return 9
    else:
        return exec_op(arr[0], 9, arr[1])

def plus(num):  # your code here
    return ["+", num]

def exec_op(op, n1, n2):
  switcher = {
        "+": operator.add,
        "-": operator.sub,
        "*": operator.mul,
        "/": operator.floordiv
    }
  return switcher[op](n1, n2)

print(nine(plus(nine())))
# 18

The other way: make plus return a function

Here is another way, which doesn't store the operations as lists of strings and numbers, and does not require an extra function operator/exec_op to evaluate the operation.

We are going to make plus(y) literally return the function ? + y.

def plus(y):
  return lambda x: x+y

def nine(op=None):
  if op is None:
    return 9
  else:
    return op(9)

print(nine(plus(nine())))
# 18

We can simplify this further by using a function instead of None as the default argument for nine:

identity = lambda x: x

def plus(y):
  return lambda x: x+y

def nine(op=identity):
  return op(9)

print(nine(plus(nine())))
# 18
Related