Nested Scopes unintended behaviour ?

Discussion in 'Python' started by Michael Sparks, Mar 17, 2010.

  1. Hi,


    Is the following behaviour expected ?

    Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
    [GCC 4.4.1] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def Toggler(F, B):

    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    ....
    >>> def Switcher(A,B):

    .... on=True
    .... def swtchfun(msg):
    .... on_ = on
    .... if on:
    .... on = False
    .... print "Switched to A"
    .... return A
    .... else:
    .... print "Switched to B"
    .... return B
    .... #
    .... return Toggler(swtchfun,True)
    ....
    >>> Switcher(1,2)

    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 13, in Switcher
    File "<stdin>", line 2, in Toggler
    File "<stdin>", line 4, in swtchfun
    UnboundLocalError: local variable 'on' referenced before assignment

    The reason I ask is because logically it makes sense. The on_ = on
    statement should resolve "on" to be the value on in the enclosing
    scope, however it appears that the "on = False" statement is taking
    priority. The reason I say this is because if you remove the "on =
    False" line you get the expected name resolution:

    >>> def Toggler(F, B):

    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    ....
    >>> def Switcher(A,B):

    .... on=True
    .... def swtchfun(msg):
    .... on_ = on
    .... if on:
    .... print "Switched to A"
    .... return A
    .... else:
    .... print "Switched to B"
    .... return B
    .... #
    .... return Toggler(swtchfun,True)
    ....
    >>> Switcher(1,2)

    Switched to A
    1

    ie it looks like python is not looking at the expected scope in the
    first instance.

    To me it looks like a bug, but I can also see a rationale where it's
    considered a feature (because the "on" is on the left hand side
    resolving the value to a local, rather than a value in an enclosed
    scope)

    I know that you can work around this as follows:
    Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
    [GCC 4.4.1] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> def Toggler(F, B):

    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    .... print F("Hello")
    ....
    >>> def Switcher(A,B):

    .... def switchgen():
    .... while True:
    .... yield A
    .... yield B
    .... G = switchgen()
    .... def swtchfun(msg):
    .... return G.next()
    .... #
    .... return Toggler(swtchfun,True)
    ....
    >>>
    >>> Switcher(1,2)

    1
    2
    1
    2

    But I'm curious as to whether the nested scope issue above is
    considered a bug or a feature, so I can deal with it appropriately.

    Any comments welcome :)

    Regards,


    Michael.
    --
    http://yeoldeclue.com/blog
    http://www.kamaelia.org/Home.html
    http://twitter.com/kamaelian
     
    Michael Sparks, Mar 17, 2010
    #1
    1. Advertising

  2. On 3/17/2010 8:16 AM Michael Sparks said...
    > Hi,
    >
    >
    > Is the following behaviour expected ?


    In short, yes. Assignment within a function forces the variable to
    locals. You can get around it like:

    >
    > Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
    > [GCC 4.4.1] on linux2
    > Type "help", "copyright", "credits" or "license" for more information.
    >>>> def Toggler(F, B):

    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ...
    >>>> def Switcher(A,B):

    > ... on=True
    > ... def swtchfun(msg):


    ---> def swtchfun(msg, on=on):

    > ... on_ = on
    > ... if on:
    > ... on = False
    > ... print "Switched to A"
    > ... return A
    > ... else:
    > ... print "Switched to B"
    > ... return B
    > ... #
    > ... return Toggler(swtchfun,True)
    > ...
    >>>> Switcher(1,2)

    > Traceback (most recent call last):
    > File "<stdin>", line 1, in<module>
    > File "<stdin>", line 13, in Switcher
    > File "<stdin>", line 2, in Toggler
    > File "<stdin>", line 4, in swtchfun
    > UnboundLocalError: local variable 'on' referenced before assignment
    >
    > The reason I ask is because logically it makes sense. The on_ = on
    > statement should resolve "on" to be the value on in the enclosing
    > scope, however it appears that the "on = False" statement is taking
    > priority. The reason I say this is because if you remove the "on =
    > False" line you get the expected name resolution:
    >
    >>>> def Toggler(F, B):

    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ...
    >>>> def Switcher(A,B):

    > ... on=True
    > ... def swtchfun(msg):
    > ... on_ = on
    > ... if on:
    > ... print "Switched to A"
    > ... return A
    > ... else:
    > ... print "Switched to B"
    > ... return B
    > ... #
    > ... return Toggler(swtchfun,True)
    > ...
    >>>> Switcher(1,2)

    > Switched to A
    > 1
    >
    > ie it looks like python is not looking at the expected scope in the
    > first instance.
    >
    > To me it looks like a bug, but I can also see a rationale where it's
    > considered a feature (because the "on" is on the left hand side
    > resolving the value to a local, rather than a value in an enclosed
    > scope)
    >
    > I know that you can work around this as follows:
    > Python 2.6.4 (r264:75706, Dec 7 2009, 18:45:15)
    > [GCC 4.4.1] on linux2
    > Type "help", "copyright", "credits" or "license" for more information.
    >>>> def Toggler(F, B):

    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ... print F("Hello")
    > ...
    >>>> def Switcher(A,B):

    > ... def switchgen():
    > ... while True:
    > ... yield A
    > ... yield B
    > ... G = switchgen()
    > ... def swtchfun(msg):
    > ... return G.next()
    > ... #
    > ... return Toggler(swtchfun,True)
    > ...
    >>>>
    >>>> Switcher(1,2)

    > 1
    > 2
    > 1
    > 2
    >
    > But I'm curious as to whether the nested scope issue above is
    > considered a bug or a feature, so I can deal with it appropriately.
    >
    > Any comments welcome :)
    >
    > Regards,
    >
    >
    > Michael.
    > --
    > http://yeoldeclue.com/blog
    > http://www.kamaelia.org/Home.html
    > http://twitter.com/kamaelian
    >
    >
    >
     
    Emile van Sebille, Mar 17, 2010
    #2
    1. Advertising

  3. Michael Sparks

    Terry Reedy Guest

    On 3/17/2010 11:44 AM, Emile van Sebille wrote:
    > On 3/17/2010 8:16 AM Michael Sparks said...
    >> Hi,
    >>
    >>
    >> Is the following behaviour expected ?

    >
    > In short, yes. Assignment within a function forces the variable to
    > locals.


    In 3.x, one can declare names to be nonlocal (ie, local to some outer
    function, as opposed to local to the current function or module global).
    In your case,
    nonlocal on
    in your inner swtchfun function would give the behavior you wanted.

    Terry Jan Reedy
     
    Terry Reedy, Mar 17, 2010
    #3
  4. On Mar 17, 8:29 pm, Terry Reedy <> wrote:
    > On 3/17/2010 11:44 AM, Emile van Sebille wrote:
    >
    > > On 3/17/2010 8:16 AM Michael Sparks said...
    > >> Hi,

    >
    > >> Is the following behaviour expected ?

    >
    > > In short, yes. Assignment within a function forces the variable to
    > > locals.

    >
    > In 3.x, one can declare names to be nonlocal (ie, local to some outer
    > function, as opposed to local to the current function or module global).
    > In your case,
    >    nonlocal on
    > in your inner swtchfun function would give the behavior you wanted.


    Ah, excellent. That makes python closures work more like I'd expect
    them to. (A colleague had written the swtchfun I posted, whereas the
    generator form was the version I wrote, and he was puzzled as to why
    it didn't work as he expected. When I saw it I also couldn't see why.

    After hearing it's expected behaviour in 2.6 it's clear that assigning
    a name to a value declares the variable to be local, and that unlike
    much of python (but like yield) this appears based on static analysis
    of the function declaration, rather than dynamic. This does also make
    sense since it prevents a name "switching scope" in a function, and a
    "nonlocal" keyword also makes sense as a result.

    Thanks to Emile for pointing out you can also do this in 2.6:
    def Toggler(F, B):
    print F("Hello")
    print F("Hello")
    print F("Hello")
    print F("Hello")
    print F("Hello")

    def Switcher(A,B):
    enclose={"on" : True}
    def swtchfun(msg, enclose=enclose):
    if enclose["on"]:
    enclose["on"] = False
    print "Switched to A"
    return A
    else:
    enclose["on"] = True
    print "Switched to B"
    return B
    #
    return Toggler(swtchfun,True)

    Switcher(1,2)


    I think personally I'd use the generator form myself, since I think
    it's clearer (more clearly loops between the two), but this may be a
    useful thing to know occasionally.

    Cheers :)


    Michael.
     
    Michael Sparks, Mar 18, 2010
    #4
  5. Michael Sparks

    Terry Reedy Guest

    On 3/18/2010 6:21 AM, Michael Sparks wrote:

    > After hearing it's expected behaviour in 2.6 it's clear that assigning
    > a name to a value declares the variable to be local,


    unless there is a global/nonlocal declaration

    and that unlike
    > much of python (but like yield) this appears based on static analysis
    > of the function declaration, rather than dynamic.


    The language definition requires two passes after parsing.
    One collects names and determines their scope (and looks for yield). The
    second generates code.
    This allows the local namespace to be implemented as an array rather
    than a dict, so that local name lookup is an array index operation
    rather than a dict lookup operation. This is somewhat made visible by
    the dis module

    >>> from dis import dis
    >>> a = 1
    >>> def f():

    b = 2
    return a,b

    >>> dis(f)

    2 0 LOAD_CONST 1 (2)
    3 STORE_FAST 0 (b)

    3 6 LOAD_GLOBAL 0 (a)
    9 LOAD_FAST 0 (b)
    12 BUILD_TUPLE 2
    15 RETURN_VALUE

    STORE/LOAD_FAST means store/load_local. Constants (2 in this case) are
    stored in the same array. There is apparently a separate array of global
    names, as opposed to local values.

    Terry Jan Reedy
     
    Terry Reedy, Mar 18, 2010
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Miki Tebeka

    Question about nested scopes

    Miki Tebeka, Oct 8, 2003, in forum: Python
    Replies:
    3
    Views:
    296
    Miki Tebeka
    Oct 9, 2003
  2. Fernando Rodriguez
    Replies:
    2
    Views:
    285
    Alexander Schmolck
    Nov 21, 2003
  3. Nils Grimsmo

    nested function scopes

    Nils Grimsmo, Dec 11, 2003, in forum: Python
    Replies:
    3
    Views:
    324
    Emile van Sebille
    Dec 14, 2003
  4. Dave Benjamin

    Nested scopes and class variables

    Dave Benjamin, Jan 31, 2005, in forum: Python
    Replies:
    7
    Views:
    398
    Steven Bethard
    Feb 3, 2005
  5. Michal
    Replies:
    1
    Views:
    83
    Robert Klemme
    Aug 27, 2004
Loading...

Share This Page