Why doesn't this closure modify the variable in the enclosing scope?

后端 未结 4 947
-上瘾入骨i
-上瘾入骨i 2020-12-15 05:59

This bit of Python does not work:

def make_incrementer(start):
    def closure():
        # I know I could write \'x = start\' and use x - that\'s not my poi         


        
相关标签:
4条回答
  • 2020-12-15 06:31

    Whenever you assign a variable inside of a function it will be a local variable for that function. The line start += 1 is assigning a new value to start, so start is a local variable. Since a local variable start exists the function will not attempt to look in the global scope for start when you first try to access it, hence the error you are seeing.

    In 3.x your code example will work if you use the nonlocal keyword:

    def make_incrementer(start):
        def closure():
            nonlocal start
            while True:
                yield start
                start += 1
        return closure
    

    On 2.x you can often get around similar issues by using the global keyword, but that does not work here because start is not a global variable.

    In this scenario you can either do something like what you suggested (x = start), or use a mutable variable where you modify and yield an internal value.

    def make_incrementer(start):
        start = [start]
        def closure():
            while True:
                yield start[0]
                start[0] += 1
        return closure
    
    0 讨论(0)
  • 2020-12-15 06:32

    There are two "better" / more Pythonic ways to do this on Python 2.x than using a container just to get around the lack of a nonlocal keyword.

    One you mentioned in a comment in your code -- bind to a local variable. There is another way to do that:

    Using a default argument

    def make_incrementer(start):
        def closure(start = start):
            while True:
                yield start
                start += 1
        return closure
    
    x = make_incrementer(100)
    iter = x()
    print iter.next()
    

    This has all the benefits of a local variable without an additional line of code. It also happens on the x = make_incrememter(100) line rather than the iter = x() line, which may or may not matter depending on the situation.

    You can also use the "don't actually assign to the referenced variable" method, in a more elegant way than using a container:

    Using a function attribute

    def make_incrementer(start):
        def closure():
            # You can still do x = closure.start if you want to rebind to local scope
            while True:
                yield closure.start
                closure.start += 1
        closure.start = start
        return closure
    
    x = make_incrementer(100)
    iter = x()
    print iter.next()    
    

    This works in all recent versions of Python and utilizes the fact that in this situation, you already have an object you know the name of you can references attributes on -- there is no need to create a new container for just this purpose.

    0 讨论(0)
  • 2020-12-15 06:36

    In Python 3.x you can use the nonlocal keyword to rebind names not in the local scope. In 2.x your only options are modifying (or mutating) the closure variables, adding instance variables to the inner function, or (as you don't want to do) creating a local variable...

    # modifying  --> call like x = make_incrementer([100])
    def make_incrementer(start):
        def closure():
            # I know I could write 'x = start' and use x - that's not my point though (:
            while True:
                yield start[0]
                start[0] += 1
        return closure
    
    # adding instance variables  --> call like x = make_incrementer(100)
    def make_incrementer(start):
        def closure():
            while True:
                yield closure.start
                closure.start += 1
        closure.start = start
        return closure
    
    # creating local variable  --> call like x = make_incrementer(100)
    def make_incrementer(start):
        def closure(start=start):
            while True:
                yield start
                start += 1
        return closure
    
    0 讨论(0)
  • 2020-12-15 06:51

    Example

    def make_incrementer(start):
        def closure():
            # I know I could write 'x = start' and use x - that's not my point though (:
            while True:
                yield start[0]
                start[0] += 1
        return closure
    
    x = make_incrementer([100])
    iter = x()
    print iter.next()
    
    0 讨论(0)
提交回复
热议问题