Python Forum
Help in understanding scope of dictionaries and lists passed to recursive functions
Thread Rating:
  • 1 Vote(s) - 2 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help in understanding scope of dictionaries and lists passed to recursive functions
#1
I'm going to try explaining what I understand here, and hopefully someone can help me understand this. I'm running python 3.6.1 on CentOS 7.

I'm looking at 2 pieces of code, one is a recursive Depth First Search algorithm, and the other is a recursive fibonacci algorithm that uses memoization to store fibonacci numbers that have already been calculated. The DFS algorithm takes a list named 'path' as a parameter, and the fibonacci algorithm takes a dictionary named 'memo' as a parameter. I'll post the code for the algorithms and then continue with my question.

DFS and Related Classes
class Node(object):
    def __init__(self, name):
        self.name = name
    def getName(self):
        return self.name
    def __str__(self):
        return self.name

class Digraph(object):
    def __init__(self):
        self.nodes = []
        self.edges = {}
    def addNode(self, node):
        if node in self.nodes:
            raise ValueError('Duplicate node')
        else:
            self.nodes.append(node)
            self.edges[node] = []
    def addEdge(self, edge):
        src = edge.getSource()
        dest = edge.getDestination()
        if not (src in self.nodes and dest in self.nodes):
            raise ValueError('Node not in graph')
        else:
            self.edges[src].append(dest)
    def childrenOf(self, node):
        return self.edges[node]
    def hasNode(self, node):
        return node in self.nodes
    def __str__(self):
        result = ''
        for src in self.nodes:
            for dest in self.edges[src]:
                result = result + src.getName() + '->' + dest.getName() + '\n'
        return result[:-1] # remove the trailing newline

def printPath(path):
    """Assumes path is a list of nodes"""
    result = ''
    for i in range(len(path)):
        result += str(path[i])
        if i != len(path) - 1:
            result += '->'
    return result

def DFS(graph, start, end, path, shortest, toPrint = False):
    """Assumes graph is a Digraph; start and end are nodes;
        path and shortest are lists of nodes"""
    path = path + [start]
    if toPrint:
        print('Current DFS path:', printPath(path))
    if start == end:
        return path
    for node in graph.childrenOf(start):
        if node not in path: #avoid cycles
            if shortest == None or len(path) < len(shortest):
                newPath = DFS(graph, node, end, path, shortest, toPrint)
                if newPath != None:
                    shortest = newPath
    return shortest
Recursive Fibonacci
def fastFib(n, memo = {}):
    """Assumes n is an int >= 0, memo used only by recursive calls
    Returns fibonacci of n"""
    if n == 0 or n == 1:
        return 1
    try:
        return memo[n]
    except KeyError:
        result = fastFib(n-1, memo) + fastFib(n-2, memo)
        memo[n] = result
        return result
Now, when studying fastFib, I initially thought that whenever memo was updated with a newly calculated fib(n), that updated memo was only visible inside of the frame associated with the recursive call to fasFib() that calculated the value. Someone explained to me that mutable types are passed by reference, and that there is only one memo and it exists in the frame of the initial call. Any updates to the contents of memo made by further recursive calls actually update that one memo from the initial frame, and so its contents are visible to in all of the other frames arising from the recursive calls to fastFib.

I thought I understood this until I studied the DFS algorithm. I would have expected path to behave in DFS the same way memo did in fastFib. It doesn't. When the contents of path get updated, the new content is only visible in the frame associated with the recursive call to DFS that updated path. So, now I'm confused. I do realize that this is actually what allows DFS to return to a previous node and check a different pathway in a graph. But I don't understand why. And now I'm not sure I understand why memo behaves the way it does.

I could use some help wrapping my head around this.
Reply
#2
In DFS, the line path = path + [start] creates a new list every time it is called. After this line, the old value of path is no longer available in the current scope. On the contrary, fastFib's recursive calls always use the same dictionary instance.
Reply
#3
Thank you! Basic enough thing in hindsight but I swear I was willing to spend all my remaining time in the course trying to get this.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How to receive two passed cmdline parameters and access them inside a Python script? pstein 2 339 Feb-17-2024, 12:29 PM
Last Post: deanhystad
  How to create a variable only for use inside the scope of a while loop? Radical 10 1,684 Nov-07-2023, 09:49 AM
Last Post: buran
  Library scope mike_zah 2 836 Feb-23-2023, 12:20 AM
Last Post: mike_zah
  Scope of variable confusion Mark17 10 2,828 Feb-24-2022, 06:03 PM
Last Post: deanhystad
  Variable scope issue melvin13 2 1,529 Nov-29-2021, 08:26 PM
Last Post: melvin13
  detecting a generstor passed to a funtion Skaperen 9 3,570 Sep-23-2021, 01:29 AM
Last Post: Skaperen
  Combine Two Recursive Functions To Create One Recursive Selection Sort Function Jeremy7 12 7,355 Jan-17-2021, 03:02 AM
Last Post: Jeremy7
  Variable scope - "global x" didn't work... ptrivino 5 3,034 Dec-28-2020, 04:52 PM
Last Post: ptrivino
  Python Closures and Scope muzikman 2 1,799 Dec-14-2020, 11:21 PM
Last Post: muzikman
  how to modify a variable that is passed as parameter StefanL38 2 2,111 Dec-07-2020, 08:39 AM
Last Post: StefanL38

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020