Python Syntax Warning For Global Variable Assignment - python

Similar questions to this have been asked and answered but none dealt with this specific question.
global EXAMPLE_GLOBAL
EXAMPLE_GLOBAL = True
def Setup():
if EXAMPLE_GLOBAL:
try:
#Doing some file io here
except:
global EXAMPLE_GLOBAL
EXAMPLE_GLOBAL = False
When running this code (Not this code exactly, this is just stripped down to the basic logic.) I get:
SyntaxWarning: name 'EXAMPLE_GLOBAL' is used prior to global declaration
global EXAMPLE_GLOBAL
I was under the impression that to change the value of a global variable you must first specify that you were referencing the global variable when you went to a new scope. Do I need the second 'global EXAMPLE_GLOBAL' in order to make the change to the global or is using the same identifier enough?

I was under the impression that to change the value of a global variable you must first specify that you were referencing the global variable when you went to a new scope
Yes, that's right. But conventionally, the global statement should go at the opening of that scope. In other words, put it at the top of your function.
EXAMPLE_GLOBAL = True
def Setup():
global EXAMPLE_GLOBAL
if EXAMPLE_GLOBAL:
try:
#Doing some file io here
except:
EXAMPLE_GLOBAL = False
The documentation explicitly recommends against referring to a name in a block before using it in a global statement:
Names listed in a global statement must not be used in the same code block textually preceding that global statement.
In CPython, violating this principle only produces a warning instead of an error because the parser ignores the specific position of the global directive within a block, but that's an implementation detail and could change in the future.
(Also note that you don't need the global statement on the first line. Every name at the top-level scope is already effectively global in that scope.)

Related

Why does writing to a variable change its scope?

Take the following code sample
var = True
def func1():
if var:
print("True")
else:
print("False")
# var = True
func1()
This prints True as one would expect.
However, if I uncomment # var = True, I get the error
UnboundLocalError: local variable 'var' referenced before assignment
Why does writing to a variable make an otherwise accessible variable inaccessible? What was the rationale behind this design choice?
Note I know how to solve it (with the global keyword). My question is why was it decided to behave this way.
Because:
Namespaces exist: the same variable name can be used at module level and inside functions, and have nothing to do with each other.
Python does not require variables to be declared, for ease of use
There still needs to be a way to distinguish between local and global variables
In cases where there is likely unexpected behavior, it is better to throw an error than to silently accept it
So Python chose the rule "if a variable name is assigned to within a function, then that name refers to a local variable" (because if it's never assigned to, it clearly isn't local as it never gets a value).
Your code could have been interpreted as using the module-level variable first (in the if: line), and then using the local variable later for the assignment. But, that will very often not be the expected behavior. So Guido decided that Python would not work like that, and throw the error instead.
Python defaults to implicit variable declaration via assignment, in order to remove the need for additional explicit declarations. Just "implicit declaration" leaves several options what assignment in nested scopes means, most prominently:
Assignment always declares a variable in the inner-most scope.
Assignment always declares a variable in the outer-most scope.
Assignment declares a variable in the inner-most scope, unless declared in any outer scope.
Assignment declares a variable in the inner-most scope, readable only after assignment.
The latter two options mean that a variable does not have a scope well-defined just by the assignment itself. They are "declaration via assignment + X" which can lead to unintended interactions between unrelated code.
That leaves the decision of whether "writing to a variable" should preferably happen to isolated local or shared global variables.
The Python designers consider it more important to explicitly mark writing to a global variable.
Python FAQ: Why am I getting an UnboundLocalError when the variable has a value?
[...]
This explicit declaration is required in order to remind you that (...) you are actually modifying the value of the variable in the outer scope
This is an intentional asymmetry towards purely reading globals, which is considered proper practice.
Python FAQ: What are the rules for local and global variables in Python?
[...]
On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you’d be using global all the time.
This is described in section 4.2.2 Resolution of names
When a name is not found at all, a NameError exception is raised. If the current scope is a function scope, and the name refers to a local variable that has not yet been bound to a value at the point where the name is used, an UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.
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.
If a variable name defined in the outer scope is used in a nested scope, it depends on what you do with it in this nested scope:
If you only read a variable, it is the same variable.
If you write to a variable, then Python automatically creates a new, local variable, different from the one in the outer scope.
This local variable prevents access to a variable with the same name in the outer scope.
So writing to a variable don't change its scope, it creates a different, local variable.
You are not able to read this local variable before assigning to it.

Local and global references with UnboundLocalError

I don't quite understand why the code
def f():
print(s)
s = "foo"
f()
runs perfectly fine but
def f():
print(s)
s = "bar"
s = "foo"
f()
gives me UnboundLocalError. I know that I can fix this by declaring s as a global variable inside the function or by simply passing s an an argument into the function.
Still I don't understand how python seemingly knows whether or not s is referenced inside the function before the line has been executed? Does python make some sort of list of all local variable references when the function is read into the global frame?
Other answers have focused on the practical aspects of this but have not actually answered the question you asked.
Yes, the Python compiler tracks which variables are assigned when it is compiling a code block (such as in a def). If a name is assigned to in a block, the compiler marks it as local.Take a look at function.__code__.co_varnames to see which variables the compiler has identified.
The nonlocal and global statements can override this.
Yes, Python will look-ahead to recover all variables declared in the local scope. These will then overshadow global variables.
So in your code:
def f():
print(s)
s = "foo"
f()
Python did not find s in the local scope, so it tries to recover it from the global scope and finds "foo".
Now in the other case the following happens:
def f():
print(s)
s = "bar
s = "foo"
f()
Python knows that s is a local variable because it did a look-ahead before runtime, but at runtime it was not assigned yet so it raised and exception.
Note that Python will even let you reference variables that have not been declared anywhere. If you do:
def foo():
return x
f()
You will get a NameError, because Python, when not finding, x as a local variable will just remember that at runtime it should look for a global variable named x and then fail if it does not exist.
So UnboundLocalError means that the variable may eventually be declared in scope but has not been yet. On the other hand NameError means that the variable will never be declared in the local scope, so Python tried to find it in the global scope, but it did not exist.

How to create a global variable if it doesn't exist in Python?

I want to define a Python function doing:
1. Check if a variable already exist.
2. If not, create it as a global variable (because I want to use it outside the function).
def foo():
try:
x
except NameError:
global x
x = 1
else:
pass
foo()
print(x)
Then there is an error:
SyntaxError: name 'x' is used prior to global declaration
How to solve this? Thank you :)
Something like this could work.
def foo():
if not('x' in locals()):
global x
x = 1
foo()
print(x)
just check if the x variable exists.
you could also check if a variable is declared as global
if not('x' in globals()):
Just declaring a variable as global at the top of your function won't create it - so this form works:
def foo():
global x
try:
x
except NameError:
x = 1
The key thing to understand here is that when Python compiles a function, it "bakes" each variable inside the function as either a local, nonlocal, or a global (or builtin) variable - any access to that variable will have either one or the other relevant bytecode. The "global" declaration thus affects the whole function, regardless of were it is - and since tryig to access a variable before the global statement would look ambiguous, the error you saw is forced.
But since you are at it - are you sure you want to do it?
global variables are good for having values that can be shared in functions in a module - but then, even if they are to be initialized in the call to an specific function, they should be declared in the module body, and properly documented. If you can't have the final value at module load time, just assign it to None.

I am getting the error 'redefined-outer-name'

When running my lint, I am getting the error below:
Redefining name 'tmp_file' from outer scope (line 38) (redefined-outer-name)
Here is my snippet of code in that line:
tmp_file = open('../build/' + me_filename + '.js','w')
That happens because you have a local name identical to a global name. The local name takes precedence, of course, but it hides the global name, makes it inaccesible, and cause confusion to the reader.
Solution
Change the local name. Or maybe the global name, whatever makes more sense. But note that the global name may be part of the public module interface. The local name should be local and thus safe to change.
Unless... your intention is for these names to be the same. Then you will need to declare the name as global in the local scope:
tmp_file = None
def do_something():
global tmp_file # <---- here!
tmp_file = open(...)
Without the global declaration, the local tmp_file will be unrelated to the global one. Hence the warning.
Solution
Create main() function which be holding all the main logic etc.
def pow(x):
return x ** 2
def add(x, y):
return x + y
def main():
x, y = 2, 4
print(pow(x))
print(add(x, y))
if __name__ == '__main__':
main()
Explanation
This works, because every new function instance creates a new local scope.
Open with with
Apart from #Rodrigo's correct answer about scopes: if your tmp_file is just that, a temporary file, you can use
with open('../build/' + me_filename + '.js','w') as tmp_file:
# do something
in both cases. It clearly defines where your tmp_file is going to be used.
It is the recommended way of dealing with variables whose scope needs to be clearly bounded.
Error description
Pylint has a built-in description:
pylint --help-msg=redefined-outer-name
gives
:redefined-outer-name (W0621): Redefining name %r from outer scope
(line %s) Used when a variable's name hide a name defined in the
outer scope. This message belongs to the variables checker.
You get this error if you have defined the same variable in multiple places like outside the def and inside the def.
If you are using the single variable define it as global variable_name and use global keyword all the places. Else please rename the other variables.

Global variable causing an error

So I am trying to declare the variable "checks" as a global variable because I get the following issue:
File "C:\Python27\Projects\Automatic Installer\autoinstall.py", line 11, in installFunc
if checks[0] == 1:
NameError: global name 'checks' is not defined
Here's my code, I've tried to add global checks to both the main body of the program as well as the installFunc function. Is there another location I should be adding it/some other way to indicate that checks should contain the information in the program?
import urllib
import subprocess
from Tkinter import *
global checks
def installFunc():
global checks
subprocess.call("md c:\MGInstall", shell=True)
subprocess.call (u"net use w: \\it01\files")
if checks[0] == 1:
subprocess.call(u"w:\\software\\snagitup.exe")
if checks[1] == 1:
subprocess.call(u"w:\\software\\camtasia.exe")
if checks[2] == 1:
urllib.urlretrieve(u"SUPERLONGURLLOLOLOL", u"c:\\MGinstall\\gotomeeting.exe")
subprocess.call (u"c:\\MGinstall\\gotomeeting.exe")
urllib.urlretrieve(u"http://ninite.com/.net-7zip-air-chrome-cutepdf-dropbox-essentials-firefox-flash-flashie-java-klitecodecs-quicktime-reader-safari-shockwave-silverlight-vlc/ninite.exe", u"c:\\MGinstall\\MGinstall.exe")
subprocess.call (u"c:\\MGinstall\\MGinstall.exe")
subprocess.call (u"w:\\printers\\installer\\printer.exe")
app = Tk()
w = Label(app, text="CompanyName IT Automatic Installer")
w.pack()
text = ["Snagit", "Camtasia", "GotoMeeting"]
variables = []
for name in text:
variables.append(IntVar())
Checkbutton(text=name, variable=variables[-1]).pack()
b = Button(text="OK", command=installFunc)
b.pack()
app.mainloop()
checks = [variable.get() for variable in variables]
I think this is because checks gets set after the mainloop (the last line of the posted code). the function installFunc gets called from the mainloop via a button press, but checks hasn't been defined yet.
Using the global data in this case isn't a good idea anyway. You should probably do something like:
def installFunc(checks):
...
checks = [variable.get() for variable in variables]
b = Button(text="OK", command=lambda : installFunc(checks))
Or, even better, wrap all this up in a class... that way you can do:
self.b=Button(..., command=self.installFunc)
Replace the first 'global checks' (the one at the global level) with 'global = ...', initializing it appropriately. Using 'global' is really relevant only within a function/method. Per the Python docs: The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global. You may want to read this as well - has lots of relevant info: Using global variables in a function other than the one that created them
The problem is not the first 'global checks'. What causes the error is that checks is accessed before it is initialized.
You must initialize checks before calling the main loop of the application.
First of all, Python is not PHP.
You need to use keyword global if an only if, you're going to assign to global variable within scope of a function.
global checks at top level makes no sense at all, and more importantly does not define that variable.
global checks in your installFunc() does not make sense, as you do not assign anything to that variable, in fact you don't even modify it.
In Python variables from outer scope are visible in local scope, unless of course you try to assign something, which would be interpreted as redefining that variable within local scope.
Problem with your code is that you only define checks after exiting main loop. Correct code would look like this
import urllib
import subprocess
from Tkinter import *
#no global definition here...
def installFunc():
#no global definition here...
subprocess.call("md c:\MGInstall", shell=True)
...
...
#define checks before starting main loop
checks = [variable.get() for variable in variables]
app.mainloop()

Categories

Resources