0

I tried to write a string matching program using a recursive function. The function first creates an empty list called tuple1 to add the points. Then it returns the list. However, when I try to use this function twice, the function adds the points to the list created in the previous function. Why doesn't the function use the default value tuple1 = [] in the second call? Any ideas??

The output of the program:

[0, 3, 5, 9, 11, 12, 15, 19]

[0, 3, 5, 9, 11, 12, 15, 19, 0, 5, 15]

btw this is an assignment from OpenCourseWare shared by MIT.

def subStringMatchExact(target, key, counter=0, tuple1=[]):
    if len(target) < len(key):
       return tuple1

    else:
       counter += 1

       if target[:len(key)] == key:
          tuple1.append(counter-1)
       return subStringMatchExact(target[1:], key, counter, tuple1)

print(subStringMatchExact("atgacatgcacaagtatgcat", "a"))
print(subStringMatchExact("atgacatgcacaagtatgcat", "atg"))
sorccery
  • 33
  • 3
  • 5
    See this question: https://stackoverflow.com/q/1132941/494134 – John Gordon Mar 31 '20 at 14:59
  • 1
    @JohnGordon thank you for the comment. That's exactly my problem. – sorccery Mar 31 '20 at 15:03
  • 1
    Default parameters in functions are bound to the function declaration, so you get a default variable as an instance, not a fresh copy every time you call the function, it's a classic gotcha in python. To get what you want to do: you define the list in the body of the function. – garglblarg Mar 31 '20 at 15:03
  • @garglblarg if I do that won't the function return an empty list since it is recursive? – sorccery Mar 31 '20 at 15:11
  • 1
    Hint: Don't name your variable `tuple1` when it is in fact a `list`, especially one that is mutated. – MisterMiyagi Mar 31 '20 at 15:16
  • A key point to remember here is that default args are *always* evaluated when the function object is originally created, not when it's called. – PM 2Ring Mar 31 '20 at 15:22

3 Answers3

3

A workaround is:

def foo(x=None):
    if x is None:
        x = []
    # do stuff

You can read more here http://effbot.org/zone/default-values.htm

2

Because the default value is a reference to the default value, it's not created every time. Hence if you run a sample like that:

def f(x=[]):
    x.append(1)
    return x
print(f()) #prints [1]
print(f()) #prints [1,1]

A workaround can be using a tuple which is immutable and converting it to a list:

def f(x=()):
    if not isinstance(x, list):
        input = list(x)
    else:
        input = x
    input.append(1)
    return input
print(f()) #[1]
print(f()) #[1]

With that way it will work

  • 1
    Thank you for the comment. I guess it is because "everything is an object" concept. Am I right? – sorccery Mar 31 '20 at 15:07
  • 1
    I guess that the reason is that the default value is stored "compile time", and then a reference is generated. Every time you call the function the default value is loaded from that memory address, so if it's modified the modified version of it will be loaded. – Alessandro_Tonali Mar 31 '20 at 15:13
1

When you use a mutable object as a default value, here comes the confusion. Here, tuple1 just adds to the list every time you call your function. The following is a work around:

def subStringMatchExact(target, key, counter=0, tuple1=None):
    if tuple1 == None:
        tuple1 = []
    if len(target) < len(key):
       return tuple1

    else:
       counter += 1

       if target[:len(key)] == key:
          tuple1.append(counter-1)
       return subStringMatchExact(target[1:], key, counter, tuple1)
Ahmed Khaled
  • 121
  • 1
  • 5