fundamentals
You are making this more challenging for yourself because you are tangling your sum function with the class. The sum function simply needs to work on a list but the context of your object, self
, is making it hard for you to focus.
Recursion is a functional heritage and this means writing your code in a functional and modular way. It's easier to read/write small, single-purpose functions and it promotes reuse within other areas of your program. Next time you need to sum a list in another class, do you want to rewrite sumRecursive
again?
Write your summing function once and then import
it where it's needed -
# mymath.py
def mysum(t):
if not t:
return 0
else:
return t.pop() + mysum(t)
See how mysum
has no concern for context, self
? Why should it? All it does it sum the elements of a list.
Now write your recursive
module -
# recursive.py
from mymath import mysum
class Recursive:
def __init__(self, arr): self.arr = arr
def sum(self): return mysum(self.arr)
See how Recursive.sum
just hands off self.arr
to mysum
? It doesn't have to be more complicated than that. mysum
will work on every list, not just lists within your Recursive
module. We don't have to know how mysum
works, that is the concern of the mymath
module.
Now we write the main
module. Each module represents a barrier of abstraction. This means that the details of a module shouldn't spill over into other modules. We don't know how Recursive
actually sums the input, and from the caller's point of view, we don't care. That is the concern of Recursive
module.
# main.py
from recursive import Recursive
recur = Recursive([1,2,3])
print(recur.sum())
6
going functional
Above we wrote mysum
using the .pop
technique in your question. I did this because that seems to be how you are understanding the problem right now. But watch what happens when we do this -
x = [1,2,3]
print(mysum(x)) # 6
print(mysum(x)) # 0
Why does the mysum
return a different answer the second time? Because as it is written now, mysum
uses t.pop()
, which mutates t
. When mysum
is finished running, t
is completely emptied!
By why would we write our function like this? What if 5 + x
returned a different result each time we called it?
x = 3
print(5 + x) # 8
print(5 + x) # 8
How annoying it would be if we could not depend on values not to change. The sum of the input, [1,2,3]
, is 6
. But as it is written, the sum of the input is to return 6
and empty the input. This second part of emptying (changing) the input is known as a side effect. Ie, the desired effect is to sum and emptying of the input list is unintended but a consequence of us using .pop
to calculate the result. This is not the functional way. Functional style means avoiding mutations, variable reassignments, and other side effects.
def mysum(t):
if not t:
return 0
else:
return t[0] + mysum(t[1:])
When written in this way, t
, is not changed. This allows us to use equational reasoning whereby we can substitute any function call for its return value and always get the correct answer
x = [1,2,3]
mysum(x)
== 1 + mysum([2,3])
== 1 + 2 + mysum([3])
== 1 + 2 + 3 + mysum([])
== 1 + 2 + 3 + 0
== 1 + 2 + 3
== 1 + 5
== 6
And x
was not changed as a result of running mysum
-
print(x)
# [1,2,3]
Note, the Recursive
module does not need to make any change to receive the benefit of rewriting mysum
in this way. Before the change, we would've seen this behavior -
# main.py
from recursive import Recursive
recur = Recursive([1,2,3])
print(recur.sum())
print(recur.sum())
6
0
Because the first call to sum
passes self.arr
to mysum
which empties self.arr
as a side effect. A second call to recur.sum()
will sum an empty self.arr
! After fixing mysum
we get the intended behaviour -
# main.py
from recursive import Recursive
recur = Recursive([1,2,3])
print(recur.sum())
print(recur.sum())
6
6
additional reading
I've written extensively about the techniques used in this answer. Follow the links to see them used in other contexts with additional explanation provided -