Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function that returns an accumulator in Python

I am reading Hackers and Painters and am confused by a problem mentioned by the author to illustrate the power of different programming languages.

The problem is:

We want to write a function that generates accumulators—a function that takes a number n, and returns a function that takes another number i and returns n incremented by i. (That’s incremented by, not plus. An accumulator has to accumulate.)

The author mentions several solutions with different programming languages. For example, Common Lisp:

(defun foo (n)
  (lambda (i) (incf n i)))

and JavaScript:

function foo(n) { return function (i) { return n += i } }

However, when it comes to Python, the following codes do not work:

def foo(n):
    s = n
    def bar(i):
        s += i
        return s
    return bar

f = foo(0)
f(1)  # UnboundLocalError: local variable 's' referenced before assignment

A simple modification will make it work:

def foo(n):
    s = [n]
    def bar(i):
        s[0] += i
        return s[0]
    return bar

I am new to Python. Why doesn the first solution not work while the second one does? The author mentions lexical variables but I still don't get it.

like image 433
madnerd Avatar asked Mar 01 '18 09:03

madnerd


People also ask

What is an accumulator function in Python?

The accumulator pattern is used to collect information from a sequence and store the result in a variable. Core to the idea is that we initiate an accumulator variable (in the following examples called acc ) and modify that variable when iterating over the collection.

What is accumulator list in Python?

We can accumulate values into a list rather than accumulating a single numeric value. Consider, for example, the following program which transforms a list into a new list by squaring each of the values. Save & Run.

What is an accumulator loop?

Inside the for loop, the update occurs. runningtotal is reassigned a new value which is the old value plus the value of x . This pattern of iterating the updating of a variable is commonly referred to as the accumulator pattern. We refer to the variable as the accumulator. This pattern will come up over and over again.


2 Answers

An infinite generator is one implementation. You can call __next__ on a generator instance to extract successive results iteratively.

def incrementer(n, i):
    while True:
        n += i
        yield n

g = incrementer(2, 5)

print(g.__next__())  # 7
print(g.__next__())  # 12
print(g.__next__())  # 17

If you need a flexible incrementer, one possibility is an object-oriented approach:

class Inc(object):
    def __init__(self, n=0):
        self.n = n
    def incrementer(self, i):
        self.n += i
        return self.n

g = Inc(2)

g.incrementer(5)  # 7
g.incrementer(3)  # 10
g.incrementer(7)  # 17
like image 33
jpp Avatar answered Oct 06 '22 22:10

jpp


s += i is just sugar for s = s + i.*

This means you assign a new value to the variable s (instead of mutating it in place). When you assign to a variable, Python assumes it is local to the function. However, before assigning it needs to evaluate s + i, but s is local and still unassigned -> Error.

In the second case s[0] += i you never assign to s directly, but only ever access an item from s. So Python can clearly see that it is not a local variable and goes looking for it in the outer scope.

Finally, a nicer alternative (in Python 3) is to explicitly tell it that s is not a local variable:

def foo(n):
    s = n
    def bar(i):
        nonlocal s
        s += i
        return s
    return bar

(There is actually no need for s - you could simply use n instead inside bar.)

*The situation is slightly more complex, but the important issue is that computation and assignment are performed in two separate steps.

like image 200
MB-F Avatar answered Oct 07 '22 00:10

MB-F