UnboundLocalError with extra code after return

Albert Hopkins marduk at letterboxes.org
Wed Sep 30 01:02:42 EDT 2009


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()
>>>






More information about the Python-list mailing list