Reputation: 2009
I'll post my code first, and then ask questions.
def menu(**arg):
if len(arg) == 0:
name = raw_input("Enter your name: ")
location = raw_input("Enter your name: ")
else:
for i,j in arg.items():
globals()[i] = j
print "Name: %s | Location: %s" % (name, location)
The goal of this function is to print out something like this:
Name: someone | Location: somewhere
If I just input
menu()
OK, no problem, but if I type
menu(name='someone', location='somewhere')
something went wrong...
If I rewrite it like this:
def menu(**arg):
if len(arg) == 0:
pass
else:
for i,j in arg.items():
globals()[i] = j
print "Name: %s | Location: %s" % (name, location)
and I type
menu(name='someone', location='somewhere')
it worked... but I don't know why
Also, why can't I replace vars() with globals() in the "rewritten version"?
And my last question is ...
I found this code verbose and redundant..
Is there any way to make it cleaner and neater?
Thanks for your patience!
Upvotes: 2
Views: 252
Reputation: 150977
First of all, don't modify globals
-- that's unnecessary and overly complex. If len(args) == 0
, just create a dictionary called args
with a name
and a location
value.
The actual problem with the code, however, is that once you define location
and name
anywhere in the function -- even in an if
clause -- Python expects that location
and name
will be local variables. So then when you refer to location
and name
, Python looks for them in the local namespace, doesn't find them, and throws an UnboundLocalError
.
>>> x = 1
>>> def foo():
... if False:
... x = 2
... print x
...
>>> foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in foo
UnboundLocalError: local variable 'x' referenced before assignment
Upvotes: 11
Reputation: 214959
I'll answer your last question:
Is there any way to make it cleaner and neater?
For example:
def menu(name=None, location=None):
if name is None:
name = raw_input("Enter your name: ")
if location is None:
location = raw_input("Enter your location: ")
print "Name: %s | Location: %s" % (name, location)
Upvotes: 4
Reputation: 61519
Instead of wondering what's wrong with the code, let Python tell you.
Let's run it:
def menu(**arg):
if len(arg) == 0:
name = raw_input("Enter your name: ")
location = raw_input("Enter your name: ")
else:
for i,j in arg.items():
globals()[i] = j
print "Name: %s | Location: %s" % (name, location)
We get something like:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 8, in menu
UnboundLocalError: local variable 'name' referenced before assignment
The print
line tries to use name
. The exception indicates that this is a local variable, and that it hasn't been assigned. Of course the local variable hasn't been assigned, since we skipped the if
block where those assignments occur, and instead ran code to update globals
.
We don't want to update globals for something like this anyway; if the asked-for variables should be local, then why on earth should user-supplied variables be global? It's especially strange to take things that could have been passed in as locals, bypass that by using **args
, and then forcibly stuff them into globals instead. Just doesn't make any sense.
The remaining question: why doesn't Python check globals for the name and location? Simple: the presence of code - even if it doesn't run, because this is configured at compile-time - that assigns to name
and location
causes the compiler to recognize those as possible locals. The local scope is tighter than the global scope, and thus is preferred.
To write code that assigns to a global (without using globals()
), you need to tell Python explicitly that the name you're using should refer to a global variable, with the global
keyword.
Upvotes: 3