4

I am trying to use an if statement to check whether a variable has been assigned and is not None.

# Code that may or may not assign value to 'variable'

if variable: 
    do things

This throws an error at the line with the if statement: "UnboundLocalError: local variable 'variable' referenced before assignment".

I thought if the variable wasn't assigned it would just be interpreted as False?

I've tried if variable in locals(): to no avail.

What's going on? What can I do to achieve the result I'm looking for?

Thanks

Elazar
  • 20,415
  • 4
  • 46
  • 67
ChootsMagoots
  • 670
  • 1
  • 6
  • 19

3 Answers3

6

It's better to simply initialize x to None at the beginning and test for None (as in Ned's answer).


What's going on is that whenever you reference a variable on a "load" (i.e. rvalue), Python looks it up and requires it to exist.

If your variable is named x, the following throws:

if x in locals(): ...

And is not what you wanted, since it would have checked if the value that the variable holds is in locals(). Not whether the name x is there.

But you can do

if 'x' in locals(): ...
Elazar
  • 20,415
  • 4
  • 46
  • 67
  • It starts with a complicated explanation, then suggests an odd thing to do ('x' in locals()), then finally ends, almost as an afterthought, with the right thing to do. – Ned Batchelder Jan 25 '18 at 23:37
  • The complicated explanation is the answer to the stated question: "I am trying to use an if statement to check whether a variable has been assigned (...) What's going on?" – Elazar Jan 25 '18 at 23:38
  • True, but the real question is, "What can I do to achieve the result I'm looking for?" – Ned Batchelder Jan 25 '18 at 23:40
4

At the beginning of the function initialize the variable to None. Then later you can check it with:

if x is not None:
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
0

Your assumption was wrong. There is no such thing as a uninitialized variable in Python. Any reference to a "variable" in Python either finds an entry in the namespace of some scope (Local, Enclosing, Global, or Builtin) or it's name lookup error.

It is better to think of Python statements of the form x=y as binding statements rather than assignments. A binding associates a name with an object. The object exists independently of its bindings (instantiated by a literal expression, for example). By contrast, most of the programming languages people are more familiar with have variables ... which have an associated type and location. Python "variables" are entries in a dictionary and the id() returns the the location of the object to which a name is bound. (The fact that this is a memory location is an implementation detail; but the principle is that Python "variable names" are not like the "variables" of more conventional languages).

Here's a bit more on that: https://softwareengineering.stackexchange.com/a/200123/11737

The best way to do accomplish this is simply refrain from using names before binding values thereto. Poking around in locals() or globals() for such things is just poor, ugly, coding practice.

Jim Dennis
  • 17,054
  • 13
  • 68
  • 116
  • I agree with the moral at the end, and it's important. However, the claim 'There is no such thing as a uninitialized "variable" in Python' is just a note about your own favorite terminology. It might be helpful for some people, but it's not "true" (or false). – Elazar Jan 25 '18 at 23:51
  • It's intended to force the reader to rethink their model for how a late-binding language works. Technically Python doesn't "assign values to variables." It "binds values (objects) to names." The ***del*** operator in Python remove a name from the namespace. This *might* leave the object free for garbage collection. There's no way to "declare" a variable (associate it with a time and reserve space for it in the heap) in Python. That's now how late binding languages work. – Jim Dennis Jan 28 '18 at 14:40
  • There is no declaration in this sense, but there are declarations (even before 3.6 - a symbol in a load position) and there are variables, as a matter of definition. They work exactly like variables in java, _most_ of the time, and the difference between "names" and "addresses" is mostly an implementation detail. If you believe your description helps the reader to avoid confusion, I cannot argue; it certainly helps explaining `del` and `locals()` etc. But it comes at the cost of losing abstraction, most of the time (in good code, that follows your advice) unnecessarily. – Elazar Jan 28 '18 at 18:59
  • The model you suggest might confuse some readers if they encounter `if False: x=5; else: print(x)` inside a function, when there's a global `x`. I admit it will likely confuse beginners anyway. – Elazar Jan 28 '18 at 19:03
  • @Elazar: it's separately important to understand the LEGB scoping rules; that there are a set of dictionary like namespaces searched by the interpreter for any name resolution: Local, Enclosing, Global, and Builtins. There are some additional complexities understanding nested scope and, in Python3, the *nonlocal* keyword for explicitly accessing the enclosing scope . – Jim Dennis Jan 28 '18 at 20:23
  • My point is that the namespaces are not searched at lookup time, but at definition time (~= "compile time", for most purposes) and the existence of an actual binding is irrelevant for that search. – Elazar Jan 29 '18 at 21:17
  • The name spaces are searched at look-up and binding times. During binding the name spaces are searched and any match is then updated with the new value; if no such name is found then a local or global name will be created as appropriate. So I'm confused by what you're trying to say. – Jim Dennis Jan 29 '18 at 22:48
  • The namespace in which `x+y` are searched does not depend on the content of the dictionaries, but on a "static" notion of which lexical scope has the relevant binding. So syntactic binding occurrences (e.g. the _code_ `x = 5` somewhere in a function scope) works very much like a declaration `int x` in Java, as far as lookup goes. That's why I claim the are variables and variable declarations in Python. – Elazar Jan 30 '18 at 10:18
  • @Elazar: I think you are confusing likely readers more than helping by attempting to draw the similarity between a binding in Python and any static declaration in any other language. The code being evaluated is either in a function/method definition (which is a local scope, possibly nested) or it's in global scope. The namespaces searched are either Local (and possibly Enclosing) or just Global. It's really that simple. – Jim Dennis Jan 30 '18 at 18:59
  • In a manner of speaking, Python not only has "uninitialized variables", but that's exactly what `UnboundLocalError` denotes. (Although "unbound" is more natural terminology, of course.) But it's correct that there is no specific *initialization* process, nor a declaration process. Unlike a language like C++ which treats `int foo = 1;` as a declaration with initialization, and `int foo; foo = 1;` as a declaration followed by an assignment, Python only has `foo = 1`. – Karl Knechtel Feb 07 '23 at 02:51