This gives me a referenced before assignment error for p1
but not for count
?
class Bagadclass:
def __init__(self):
print "i n s t a n t i a t e d"
pass
def getRegion(self):
count = [0]
p1 = [0 , 0]
p2 = [0 , 0]
def on_click(x,y,button,pressed):
count[0] += 1
if count[0] > 5:
self.Image = pyautogui.screenshot(region=p1+p2)
listener.stop()
pass
if count[0] == 2:
p1 = list(pyautogui.position())
pass
if count[0] == 4:
print p1
p2 = (pyautogui.position()[0] - p1[0] , pyautogui.position()[1] - p1[1])
pass
print count , x , y , button , pressed
pass
with Listener(on_click=on_click) as listener:
listener.join()
pass
Can anyone explain why? I think this is about the listener object or something but if it worked for count
i think it should also work for the other variables.
The function on_click()
is a closure, it has access to the variables count
, p1
, and p2
, defined in the enclosing scope, getRegion()
. These variables, (count
, p1
, p2
) are known as free variables*, "If a variable is used in a code block but not defined there, it is a free variable" Link 1.
*Actually, in your example, only count
is a free variable; p1
and p2
are not free variables, as you have defined them in the local scope of on_click()
:
...
if count[0] == 2:
p1 = list(pyautogui.position()) # <-- here
pass
if count[0] == 4:
print p1
p2 = (pyautogui.position()[0] - p1[0] , pyautogui.position()[1] - p1[1]) # <-- and here
...
And so are no longer free variables, they are now local variables. In each block, a name is either a local variable, a global variable or a free variable. A name does not transform from a free variable to a local variable after the assignment statement, instead, if an assignment statement appears anywhere within a block, the name is now a local variable. If you try to access a local variable that has not yet been bound (i.e. code execution has not yet reached the assignment statement of the local variable), you will get an UnboundLocalError
exception.
From Execution model — Python 2.7.15 documentation:
If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
In essence, this basically means you can 'access' free variables (including modify/mutate them) but you can't 'write'/rebind them, Python will interpret this as defining an entirely new local variable. In Python 3, you can resolve this by using the nonlocal
keyword, otherwise a common practice is to wrap your data in another structure (e.g. dictionary or list) and then modify the structure to alter your data instead; this way, you are not attempting to rebind the free variable, merely modifying the object it refers to.
This is in fact what has already been done for the count
variable, the actual count value is stored in a single element list and when on_click()
needs to increment the count, it increments the first element of the count
list (count[0]+=1
), instead of trying to rebind it (something like count+=1
would not work).
Example of how you might revise your code (# !
indicates changes):
...
def getRegion(self):
count = [0]
p = [[0, 0], [0, 0]] # [p1, p2] # !
def on_click(x,y,button,pressed):
count[0] += 1
if count[0] > 5:
self.Image = pyautogui.screenshot(region=p[0]+p[1]) # !
...
if count[0] == 2:
p[0] = list(pyautogui.position()) # !
...
if count[0] == 4:
print p[0] # !
p[1] = (pyautogui.position()[0] - p[0][0],
pyautogui.position()[1] - p[0][1]) # !
...
Links: