1

Write a function that takes in no arguments and returns two functions, prepend and get, which represent the “add to front of list” and “get the ith item” operations, respectively. Do not use any python built-in data structures like lists or dictionaries. You do not necessarily need to use all the lines

def nonlocalist():
    get = lambda x: "Index out of range!"
    def prepend(value):
        nonlocal get
        f = get
        def get(i):
            if i == 0:
                return value
            return f(i - 1)
    return prepend, lambda x: get(x)

can someone help explain how these codes work, i have no idea from the beginning how to write it.

Klaus D.
  • 13,874
  • 5
  • 41
  • 48
Proteus Yi
  • 53
  • 5

1 Answers1

3

In a nutshell, this works by storing data in closures, and using recursion to retrieve them. As a very short primer on closures:

def foo(value):
    def bar():
        print(value)

    return bar

b = foo(42)
b()  # prints 42

The value here is being "closed over". This means even though the execution of foo has already ended and the variable value should have gone out of scope and been garbage collected, since bar refers to it, it is still kept around and is in fact accessible to bar when called.

Your linked list code uses this principle to store values in more and more nested scopes, and uses recursion to traverse back up the scopes and retrieve them.

Here's a visualisation of this abomination in action:

Pythontutor visualisation

You can play around with it yourself here.


When calling nonlocalist, you get two functions back: prepend, and a function that calls the "latest version" of get.

prepend always does the same thing:

  1. Take a value argument.
  2. Store the current version of get in f.
  3. Overwrite get with a new function which either:
    • returns value (as demonstrated in the closure example above), or
    • calls the previous version of get (now called f), which has the previous value closed over

When you call the second "getter" function that nonlocalist gave you, it:

  1. Looks up the current version of get and calls it, which
  2. returns the value it has closed over or the result of its previous version.
deceze
  • 510,633
  • 85
  • 743
  • 889
  • do you know why `return prepend, get` generates a different output than `return prepend, lambda x: get(x)`? Essentially, why should `get` need an extra wrap? – cards Jul 27 '23 at 15:37
  • 1
    That’s why I emphasized that the returned function *looks up the current `get`.* The specific function that the name `get` refers to changes, so it matters whether you return the first `get` function object, or a function that looks up the name `get` when called. – deceze Jul 27 '23 at 17:22