UnboundLocalError with extra code after return

R

Rich Healey

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
 
R

Rich Healey

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!
 
C

Chris Rebert

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
 
A

Albert Hopkins

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'
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,228
Members
46,816
Latest member
nipsseyhussle

Latest Threads

Top