local variable referenced before assignment / Python - python

I'm trying to design a counter which would be incremented each time an action is done. Something like this:
def action_on_accounts(self, accounts):
for account in accounts[9:]:
try:
self.browser.get(account)
time.sleep(5)
action_button = self.browser.find_element_by_xpath(u'//button[contains(#class, "Heart")]').click()
counter_var = self.count_actions(counter_var)
print(counter_var)
except selenium.common.exceptions.NoSuchElementException:
break
def count_actions(self, counter_var):
return counter_var + 1
def main(self):
counter_var = 0
(...)
This is throwing a UnboundLocalError: local variable 'counter_var' referenced before assignment
I have read that I have to declare counter_var as global inside function and did this:
def count_actions(self, counter_var):
global counter_var
return counter_var + 1
It was throwing SyntaxError: name 'counter_var' is parameter and global error.
So I tried this :
def count_actions(self):
global counter_var
return counter_var + 1
Calling it this way :
counter_var = self.count_actions()
print(counter_var)
And now I have NameError: name 'counter_var' is not defined....
Please assist

An alternative, simpler solution is to use python's built-in enumerate(); It exists so we don't have to make our own functions. You can then set the count equal to a global variable declared outside your functions
So the code would look like this:
counter_var = 0
def action_on_accounts(self, accounts):
for count, account in enumerate(accounts[9:]):
global counter_var
counter_var = count
print(counter_var)
def main(self):
global counter_var
counter_var = 0
(...)

You should consider defining counter_var as an attribute: self.counter_var. It'll be accessible throughout your class (assuming that's what's going on). You won't have to provide it explicitly as an argument in your functions/methods and no need to worry about global variables.
def action_on_accounts(self, accounts):
for account in accounts[9:]:
try:
self.browser.get(account)
time.sleep(5)
action_button = self.browser.find_element_by_xpath(u'//button[contains(#class, "Heart")]').click()
self.count_actions()
print(self.counter_var)
except selenium.common.exceptions.NoSuchElementException:
break
def count_actions(self):
self.counter_var += 1
def main(self):
self.counter_var = 0
(...)
You'll probably want to initialize self.counter_var with your class however.

Well if you really want to use a global variable (which I advice against) you can do it with the global keyword. You need that keyword to declare the variable. Example:
def action_on_accounts():
global counter_var #declare the variable
counter_var = count_actions(counter_var)
print(counter_var)
def count_actions(cv):
global counter_var #you do not have to declare the variable here since we are theoretically in the right scope
print(counter_var) #just to check that is global
return cv + 1 #no need to declare since it is a parameter
if __name__ == "__main__":
counter_var = 0
action_on_accounts() #1
action_on_accounts() #2
print(counter_var) #will return 2
I tested this in the IPython console (Python 3.6).
However I strongly recommend that you use attributes of classes to do the same effect (as in use self not global). Global variables may create bad code.

Related

Are there cases where global keyword is necessary in python?

I want to know if there are some cases where declaring global keyword is necessary in python.
Yes, there are some cases where global is neccessary.
Have a look at this code, which will work fine:
i = 42 # this is a global var
def f():
print(i)
But what if you would like to edit i (which is a global variable).
If you do this, you get an error:
i = 42 # this is a global var
def f():
i += 1 # this will not work
print(i)
We can only access i. If python compiles the function to bytecode it detects an assignment to a variable and it assumes it is a local variable. But this is not the case here (it is a global variable). Therefore if we also want to modify the global var i we must use the global keyword.
i = 42 # this is a global var
def f():
global i
i += 1 # this will change the global var without error
print(i)
When you have shared resources and want to make changes in global one.
a = 0
def add_five():
global a
a += 5
def remove_two():
global a
a -= 2
add_five() # a = 5
add_five() # a = 10
add_five() # a = 15
remove_two() # a = 13

NameError in functions using global variable

I try to stop a .after function i tkinter in python 3.6, but have probems with namspace of variables. I get NameError: "name 'counter' is not defined" when I use this code:
I read this excellent post, from where I got the idea of using two buttons instead of one.
Use start and stop function with same button in Tkinter
def count(ac,rs):
if ac:
global counter
counter += 1
label.config(text=counter)
label.after(1000, count)
if rs:
counter = 0
counter += 1
count()
def start():
ac = True
rs=False
count(ac,rs)
def stop():
ac = False
label.configure(text='0')
rs = True
count(ac,rs)
Since I declare the variable counter in the count() function, I don't understand the NameError.

Why is this variable not defined if it is a global variable?

I create a list and try to append it to another list, but even though it is a global list it still is not defined.
I had the same issue trying to apppend a string to another list, and that had the same error so I tried to make the string a list.
sees if the player hits
def hit_or_stand():
global hit **<-- notice hit is a global variable**
if hitStand == ("hit"):
player_hand()
card = deck()
hit = []
hit.append(card)
now I need to append hit to pHand (player's hand)
def player_hand():
global pHand
deck()
pHand = []
pHand.append(card)
deck()
pHand.append(card)
pHand.append(hit) **<--- "NameError: name 'hit' is not defined"**
pHand = (" and ").join(pHand)
return (pHand)
hit_or_stand()
player_hand()
global hit
This does not declare a variable which is global. It does not create a variable which does not exist. It simply says "if you see this name in this scope, assume it's global". To "declare" a global variable, you need to give it a value.
# At the top-level
hit = "Whatever"
# Or in a function
global hit
hit = "Whatever"
The only time you need a global declaration is if you want to assign to a global variable inside a function, as the name could be interpreted as local otherwise. For more on globals, see this question.
There is a misunderstanding of the global operation in OP's post. The global inside a function tells python to use that global variable name within that scope. It doesn't make a variable into a global variable by itself.
# this is already a global variable because it's on the top level
g = ''
# in this function, the global variable g is used
def example1():
global g
g = 'this is a global variable'
# in this function, a local variable g is used within the function scope
def example2():
g = 'this is a local variable'
# confirm the logic
example1()
print( g ) # prints "this is a global variable"
example2()
print( g ) # still prints "this is a global variable"

I need a delegate class?

I have a problem that sounds like a delegation problem. I have a code like the following:
class foo(object):
def __init__(self,onEvent1="", onEvent2=""):
self.OnEvent1=onEvent1
self.OnEvent1=onEvent2
def aLoop(self):
...
#in case of event1
self.OnEvent1()
...
#in case of event2
self.OnEvent2()
EventType=0
def MyBar1():
print("Event Type1")
EventType=1
def MyBar2():
print("Event Type2")
EventType=2
myFoo=foo(MyBar1,MyBar2)
while True:
myFoo.aLoop()
if (EventType==1):
print ("EventType is 1")
EventType=0
elif (EventType==2):
print ("EventType is 2")
EventType=0
I can see the message of the print() inside the callback functions but not the print() of the messages in the while loop.
The variable EventType doesn't change its value.
What I can do?
The EventType variables in MyBar1 and MyBar2 are local variables. Any variable you bind to is a local, unless explicitly configured otherwise; assignments, function parameters, a function or class definition and names you import are all ways to bind a name.
You need to use a global statement to change this:
def MyBar1():
global EventType
print("Event Type1")
EventType=1
def MyBar2():
global EventType
print("Event Type2")
EventType=2
Note that there is little point in giving your event arguments an empty string as default argument:
def __init__(self,onEvent1="", onEvent2=""):
If they are optional, set them to None and test for that:
def __init__(self, onEvent1=None, onEvent2=None):
self.OnEvent1 = onEvent1
self.OnEvent2 = onEvent2
def aLoop(self):
...
#in case of event1
if self.OnEvent1 is not None:
self.OnEvent1()
...
#in case of event2
if self.OnEvent2 is not None:
self.OnEvent2()
EventType=0
def MyBar1():
global EventType
print("Event Type1")
EventType=1
def MyBar2():
global EventType
print("Event Type2")
EventType=2
The problem is, you need to modify a global variable, but you're creating a local one instead. You can still access the global variable without using global variable. You need this to modify it.

Python FTP hangs in callback

I'm using ftplib to create a simple script to push out a file to multiple IP addresses, all set up as FTP servers. I wanted to display progress in the file upload process, but I'm having an issue. I use the callback argument of FTP.storbinary() and it works with something like this:
count = 0
def update(block):
count2 = str(count + 1)
print count2
However, if I try to do any arithmetic outside of a str() call, the program hangs. So the following doesn't work:
count = 0
def update(block):
count += 1
print count
Even wrapping count in a str() call doesn't work. It just hangs on the first call.
If you just try calling update yourself, instead of passing it to FTP.storbinary, you'll see the problem immediately:
>>> update('')
UnboundLocalError: local variable 'count' referenced before assignment
If you want to update a global variable, you have to mark it global explicitly:
def update(block):
global count
count += 1
print count
See the FAQ entry Why am I getting an UnboundLocalError when the variable has a value? and the following question What are the rules for local and global variables in Python?, and the docs on global, for more details.
A better way to solve this would be to write a class:
class FtpHandler(object):
def __init__(self):
self.count = 0
def update(self, block):
self.count += 1
print self.count
Then, to use it, you construct an instance of the class, and pass a bound method instead of a plain function to the FTP code. For example, instead of this:
ftp = ftplib.FTP(...)
# ...
ftp.storbinary(spam, eggs, callback=update)
… do this:
myhandler = FtpHandler()
ftp = ftplib.FTP(...)
# ...
ftp.storbinary(spam, eggs, callback=myhandler.update)
It doesn't just hang, it produces an Exception (specifically an UnboundLocalError). You're trying to modify a global variable inside of a function; to do this the variable must be declared global:
count = 0
def update(block):
global count
count += 1
print count
This is almost always a sign of bad design, in your case it would probably be better to use a class with an attribute:
class MyCallbackHandler(object):
def __init__(self):
self.count = 0
def update(self, block):
self.count += 1
#... etc.

Categories