2

I am trying to understand Python 3 variable scoping and nonlocal.

Consider the following function (it is just an example):

def build_property(something):

    def deco(func):

        def getter(self):
            return getattr(self, something)

        def setter(self, value):
            setattr(self, something, value)

        return property(getter, setter)

    return deco

This works fine without nonlocal. But if now I want to conditionally create getters and setters depending on something I need nonlocal.

def build_property(something):

    def deco(func):

        nonlocal something # This is needed

        if something.startswith('A'):
            getter = None
        else:
            def getter(self):
                return getattr(self, something)

        if something.startswith('B'):
            setter = None
        else:
            def setter(self, value):
                setattr(self, something, value)

        return property(getter, setter)

    return deco

Why is nonlocal needed in one case, but not in the other? In other word, why something if correctly found in the first case (without nonlocal), but in the second I get: "UnboundLocalError: local variable 'something' referenced before assignment" if nonlocal is not present?

Hernan
  • 5,811
  • 10
  • 51
  • 86
  • 1
    ... and your question is? You did *not* say anything about errors/unexpected behaviour. If you get an exception provide the **full** traceback in your question. If you see an unexpected behaviour you *should* provide 1) the behaviour you expect and 2) the actual output you get. We are **not** fortune-tellers. – Bakuriu Jan 11 '14 at 14:13
  • Just out of cuiosity: What is the point of your decorators? You don't seem to use `func` anywhere... – glglgl Jan 11 '14 at 15:33
  • BTW, I didn't test the code, but I don't see why it should be needed in the 2nd case, as `something` is never assigned to... – glglgl Jan 11 '14 at 15:36
  • It is simply not true that `nonlocal` is required in such a case. `nonlocal` is necessary only when you assign to `something`. – user4815162342 Jan 11 '14 at 16:34

2 Answers2

5

First: nonlocal is not necessary in the code you've written. You're not changing the object that something points to.

Second: There are cases where you would need to use nonlocal. Below is some code where nonlocal is necessary. Note that all of the assertions are correct (That is, they do not raise an AssertionError).

def main():
    variable = 1

    def function():
        variable = 2
    function()
    assert variable == 1

    def function():
        nonlocal variable
        variable = 2
    function()
    assert variable == 2

if __name__ == '__main__':
    main()

Third: The code you've presented does not produce the error that you claim it does. If I remove the nonlocal line, and call the following functions, I get no errors.

build_property('A')(lambda: True)
build_property('B')(lambda: True)
build_property('C')(lambda: True)
Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
-1
def A(d):
    outer = object()
    d["outer"] = outer
    def B():
        print locals()
        assert d["outer"] is outer #This fails and never reaches
        inner = object()
        d=dict()                   #this line.
        print locals()
    def C():
        print locals()
        assert d["outer"] is outer #This goes on fine.
        inner = object()
        print locals()
    return B,C

=> b,c = A(dict())
=> c()
-snip, AOK-
=> b()
UnboundLocalError: local variable 'd' referenced before assignment

I'm sorry, I deserve the flame. Above code I wrote up quickly, makes the answer that was previously here a bunch of nonsense.

But it's surprising. I always thought of python(2.x) as a completely foresight-less language, evaluating everything at the last moment...

Sorry for what now is off-topic.

loa_in_
  • 1,030
  • 9
  • 20
  • I wrote some stupid things, but it's about keeping references of parent scopes, yeah. – loa_in_ Jan 11 '14 at 15:17
  • This answer is incorrect. `nonlocal` is not an optimization, it is necessary to correctly identify the scope in which the variable is to be created. Python 2 doesn't keep all the enclosing locals, only those that are referenced by the closure - exactly like Python 3. The difference is that Python 3 allows you to *change* the value of the closed-over variable without creating a new variable as a side effect. `nonlocal` tells the compiler that, despite the assignment happening in an inner block, the variable being modified is the one created in the outer block. – user4815162342 Jan 11 '14 at 16:37