0

I am having issues with using function generators in python, i.e. returning functions from a function, where some of the inputs are defined via the outer function's arguments, yielding a function with lower arity. I have used this method before in other cases without issue but I have run into an error in this case and haven't been able to understand the reason. I have the following function that produces a function:

def func1(s: set, f: Callable) -> Callable:
    def func2(x: int) -> dict:
        while x:
            s = s - {x}
            x = f(x)
        return s
    return func2

f2 = func1({1,2,3,4}, lambda x: x-1)

If I try f2(3), I get the error "local variable 's' referenced before assignment" related to the s = s - {x} statement. I don't understand why s is not part of the scope since I assumed it was fixed when func1 was called to create f2.

I was able to fix this issue by instead using this method: I combine all the arguments into one function, say func3 and use the functools.partial operation, which behaves as expected:

from functools import partial
def func3(x: int, s: set, f: Callable) -> dict:
    while x:
        s = s - {x}
        x = f(x)
    return s

f2 = partial(func3, s={1,2,3,4}, f=lambda x: x-1)

When I call f2(3) I get {4} as expected.

My understanding is that both of these approaches should yield the same result; however, I am apparently missing something about how variable scope operates in the first case. Any insight would be greatly appreciated.

teepee
  • 2,620
  • 2
  • 22
  • 47
  • Does this answer your question? [Python 3: UnboundLocalError: local variable referenced before assignment](https://stackoverflow.com/questions/10851906/python-3-unboundlocalerror-local-variable-referenced-before-assignment) – Kelly Bundy Jan 22 '22 at 07:03
  • What output do you expect for `print(f2(2), f2(3), f2(2))`? – Kelly Bundy Jan 22 '22 at 07:41

1 Answers1

2

The problem with your first piece of code is that s is a local variable inside func2. It has no connection to the variable s that you've defined inside func1. Remember that any variable that is assigned to within a function is local, unless you declare it otherwise.

The fix is to add nonlocal s to your definition of func2. Python will then realize that the s referred to inside func2 is the same as the s inside func1.

@KellyBundy points out that this may still not give you exactly what you want. If you simply make s nonlocal, you are effectively saying that these two variables point point to the same set. Any changes made to s are "permanent". The second time f2 is called, the value of s will be whatever it was at the end of the first time f2 is called. It is uncertain if this is what is intended.

If you want s to be reset each time f2 is called, you need to keep the two ss separate. The simplest way would be to start off func2 with s0 = s, and then just use s0 within func2. Since sis never modified withinfunc2, it refers to the outer variable. And you have a different variable s0withinfunc2` that can be modified as you wish.

Frank Yellin
  • 9,127
  • 1
  • 12
  • 22
  • With `nonlocal` it's not equivalent to their`partial` version, though. Not clear what they really want. – Kelly Bundy Jan 22 '22 at 07:09
  • @KellyBundy. Not sure what you mean. Both return the same answer. Both close over the variable `s`, but use different syntax to do so. I'm sure internally, they're very different. – Frank Yellin Jan 22 '22 at 07:15
  • Given that they tagged `functional-programming`, whose description even says "avoiding side effects", I'm inclined to suspect this isn't what they desire. – Kelly Bundy Jan 22 '22 at 07:17
  • Try `print(f2(2), f2(3), f2(2))` with their `partial` solution and with the solution fixed like you propose. – Kelly Bundy Jan 22 '22 at 07:19
  • The specific question in the text was "I don't understand why `s` isn't in scope". I explained it to them and showed them how to fix it. – Frank Yellin Jan 22 '22 at 07:19
  • Your explanation is good. Your proposed fix is doubtful. – Kelly Bundy Jan 22 '22 at 07:24
  • Put differently: I suspect a proper fix would be to not shadow the outer variable, by calling the outer variable let's say `s0` and starting the inner function with `s = s0`. – Kelly Bundy Jan 22 '22 at 07:32
  • I see what you're saying. It's unclear what OP expects the scope of `s` is supposed to be. In one case, `s` is always `{1, 2, 3, 4}` at the start of the inner function. In the latter, it's whatever value `s` had at the last time it was called. Will add your comments. – Frank Yellin Jan 22 '22 at 07:39