UnboundLocalError with extra code after return

Discussion in 'Python' started by Rich Healey, Sep 30, 2009.

  1. Rich Healey

    Rich Healey Guest

    I'm trying to write a decorator that causes a function to do nothing
    if called more than once- the reason for this is trivial and to see if
    I can (Read- I'm enjoying the challenge, please don't ruin it for me
    =] )

    However I'm getting strange results with Python 2.6.2 on win32.

    With this code:

    def callonce(func):
    def nullmethod(): pass
    def __():
    return func()
    return __
    @callonce
    def t2():
    print "T2 called"
    t2()

    It does exactly what you'd expect, prints "T2 called"

    However:

    def callonce(func):
    def nullmethod(): pass
    def __():
    return func()
    func = nullmethod
    return ret
    return __

    @callonce
    def t2():
    print "T2 called"
    t2()

    Gives me:

    C:\tmp\callonce>callonce.py
    Traceback (most recent call last):
    File "C:\tmp\callonce\callonce.py", line 27, in <module>
    t2()
    File "C:\tmp\callonce\callonce.py", line 12, in __
    return func()
    UnboundLocalError: local variable 'func' referenced before assignment

    Any ideas on why? This looks like a bug to me, but I'm quite new to
    this style of programming so it may be some nuance I'm not aware of.

    Thanks in advance.

    Rich Healey
    Rich Healey, Sep 30, 2009
    #1
    1. Advertising

  2. Rich Healey

    Rich Healey Guest

    On Sep 30, 2:15 pm, Rich Healey <> wrote:
    > I'm trying to write a decorator that causes a function to do nothing
    > if called more than once- the reason for this is trivial and to see if
    > I can (Read- I'm enjoying the challenge, please don't ruin it for me
    > =] )
    >
    > However I'm getting strange results with Python 2.6.2 on win32.
    >
    > With this code:
    >
    > def callonce(func):
    >     def nullmethod(): pass
    >     def __():
    >         return func()
    >     return __
    > @callonce
    > def t2():
    >     print "T2 called"
    > t2()
    >
    > It does exactly what you'd expect, prints "T2 called"
    >
    > However:
    >
    > def callonce(func):
    >     def nullmethod(): pass
    >     def __():
    >         return func()
    >         func = nullmethod
    >         return ret
    >     return __
    >
    > @callonce
    > def t2():
    >     print "T2 called"
    > t2()
    >
    > Gives me:
    >
    > C:\tmp\callonce>callonce.py
    > Traceback (most recent call last):
    >   File "C:\tmp\callonce\callonce.py", line 27, in <module>
    >     t2()
    >   File "C:\tmp\callonce\callonce.py", line 12, in __
    >     return func()
    > UnboundLocalError: local variable 'func' referenced before assignment
    >
    > Any ideas on why? This looks like a bug to me, but I'm quite new to
    > this style of programming so it may be some nuance I'm not aware of.
    >
    > Thanks in advance.
    >
    > Rich Healey


    In case anyone was paying attention I've now gotten this working- and
    raised a whole bunch more questions!

    def callonce(func):
    func.__RECALL = True
    def __():
    if func.__RECALL:
    func.__RECALL = False
    return func()
    else:
    return
    return __

    @callonce
    def t2():
    print "T2 called"
    t2()
    t2()
    t2()

    Works as expected- the last two t2() calls do nothing.

    It seems that my problem was that I can't assign a new function to the
    name func within the callonce() function. I can however interact with
    the func object (in this case storing information about whether or not
    I'd called it in it's __RECALL item.

    Is there a cleaner solution?

    I'd originally wanted to define a function that does nothing, and then
    point the function to that for subsequent calls (this seems cleaner
    overall, if a little longwidned to write)

    ie:
    def onlyCalledOnce():
    global onlyCalledOnce
    def nullfunc(): pass
    onlyCalledOnce = nullfunc
    # Do stuff here
    return "Something"

    I'll keep plugging at this for a little while I think- thoughts
    suggestions welcome!
    Rich Healey, Sep 30, 2009
    #2
    1. Advertising

  3. Rich Healey

    Chris Rebert Guest

    On Tue, Sep 29, 2009 at 9:15 PM, Rich Healey <> wrote:
    > However:
    >
    > def callonce(func):
    >    def nullmethod(): pass
    >    def __():
    >        return func()
    >        func = nullmethod


    When Python sees this assignment to func as it compiles the __()
    method, it marks func as a local variable and will not consult nested
    function scopes when looking it up at runtime.
    Hence, when the function is executed, Python does a fast lookup of
    func in the local scope, finds it has not been assigned to, and raises
    the error you're seeing, not falling back to and examining the outer
    function variable scope.

    Additionally, the __() method does not make sense as written, for its
    body will stop executing as soon as it hits the first `return`
    statement.

    Cheers,
    Chris
    --
    http://blog.rebertia.com
    Chris Rebert, Sep 30, 2009
    #3
  4. Rich Healey

    Chris Rebert Guest

    On Tue, Sep 29, 2009 at 9:41 PM, Chris Rebert <> wrote:
    > On Tue, Sep 29, 2009 at 9:15 PM, Rich Healey <> wrote:
    >> However:
    >>
    >> def callonce(func):
    >>    def nullmethod(): pass
    >>    def __():
    >>        return func()
    >>        func = nullmethod


    Additionally, to rebind a variable in an outer nested function scope
    like you tried to do, you'd need a `nonlocal` statement. See
    http://www.python.org/dev/peps/pep-3104/

    Cheers,
    Chris
    --
    http://blog.rebertia.com
    Chris Rebert, Sep 30, 2009
    #4
  5. On Tue, 2009-09-29 at 21:15 -0700, Rich Healey wrote:
    > However:
    >
    > def callonce(func):
    > def nullmethod(): pass
    > def __():
    > return func()
    > func = nullmethod
    > return ret
    > return __
    >
    > @callonce
    > def t2():
    > print "T2 called"
    > t2()
    >
    > Gives me:
    >
    > C:\tmp\callonce>callonce.py
    > Traceback (most recent call last):
    > File "C:\tmp\callonce\callonce.py", line 27, in <module>
    > t2()
    > File "C:\tmp\callonce\callonce.py", line 12, in __
    > return func()
    > UnboundLocalError: local variable 'func' referenced before assignment
    >
    > Any ideas on why? This looks like a bug to me, but I'm quite new to
    > this style of programming so it may be some nuance I'm not aware of.


    I'm not following your logic. There is no check to see if func is
    already called. Moreover, you are replacing func which is not
    recommended. A decorator is supposed to "decorate" func, not replace
    it. I think what is happening here is func = nullmethod is being
    assigned at definition time, not at runtime, so by the time you've
    defined __() func is no longer there, so

    Secondly, if nullmethod returns nothing, why not just return nothing in
    __() instead of creating a new function that does nothing.

    Thirdly, 'return ret' is never called. Because you return out of __()
    in the first line of the function.

    Fourthly, 'ret' is never defined, so even if it were run you would get
    an undefined error.

    But what I think is happening is since you have effectively overriden
    func (t2) with nullmethod it gets 'lost' by the time the __() is
    actually called. I haven't looked closely but you should be able to see
    what's happening in a debugger.

    What you really want to do is something like this:

    def callonce(func):
    func.called = False
    def dec(*args, **kwargs):
    if func.called:
    return
    func.called=True
    return func(*args, **kwargs)
    return dec

    @callonce
    def t2():
    print 't2() Called'

    >>> t2()

    t2() Called
    >>> t2()
    >>>
    Albert Hopkins, Sep 30, 2009
    #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. Brad Clements

    UnboundLocalError on shadowed import

    Brad Clements, Jun 29, 2004, in forum: Python
    Replies:
    3
    Views:
    435
    Peter Hansen
    Jun 30, 2004
  2. mathieu
    Replies:
    3
    Views:
    594
    Bo Persson
    Sep 4, 2009
  3. pylearner
    Replies:
    2
    Views:
    264
    Bruno Desthuilliers
    Sep 28, 2009
  4. pylearner
    Replies:
    1
    Views:
    224
    Francesco Bochicchio
    Sep 28, 2009
  5. Replies:
    9
    Views:
    93
    John Gordon
    Jun 24, 2013
Loading...

Share This Page