I had so much fun writing the previous blog post about Python scoping that I decided to expand it into a free e-mail course. Each day (for five days), you’ll receive another lesson about how scopes work in Python, and why this is important for you to know as a Python developer.
So if you’ve ever been unclear on the “LEGB rule,” wanted to know when and how to use the “global keyword,” or even how you can nearly break your Python implementation through scope abuse, this e-mail course should help.
Please e-mail me if you have questions or comments about this e-mail course! I’ve had so much fun putting this one together that I’m very likely to create additional ones, so your suggestions for future topics are extremely welcome.
Let’s say I want to try something on a list in Python. While I usually like to call my test list objects “mylist”, I sometimes forget, and create a variable named “list”:
list = ['a', 'b', 'c']
If you’re like me, then you might not immediately notice that you’ve just defined a variable whose name is the same as a built-in type. Other languages might have defined “list” as a reserved word, such that you cannot define it. (Just try creating a variable named “if”, and you’ll see what I mean.) But Python won’t stop you. This means that now, instead of type(list) returning “type” (i.e., indicating that list is a data type), it’ll say:
>>> type(list) <type 'list'>
If you’re new to Python, and think that it’s normal for the return type of “list” to be “list”, let’s get a bit bolder:
>>> list = 'abc' >>> type(list) <type 'str'>
(If you’re using Python 3, then you’ll see “<class ‘str’>”, and not “<type ‘str’>”. But there’s really no difference.) Now things are getting downright weird. Of course, I can fix the situation:
>>> del(list) >>> type(list) <type 'type'>
What’s going on here? How was I able to turn lists into strings? And how did deleting “list” suddenly restore things? The answer is: Python’s scoping rules. They are fairly simple to understand, and very consistent (as you would expect from Python), but have implications that can cause all sorts of weirdness if you’re not sure of what to expect. The Python scoping rules are Local, Enclosing Function, Global, and Builtins, often abbreviated as “LEGB.” This means that when Python encounters an identifier (variable or function) name, it will look for the name as follows:
So if I have a one-line Python program:
there are no functions, and thus no L or E to consider. Python will look in G, the global namespace, meaning the current file. If there is an x defined there, then great; that’s what will be printed. If there is no x defined in the current file, then Python will look in __builtins__, which doesn’t have an x, and we’ll get a NameError exception — meaning, Python doesn’t know what name you’re talking about. So far, so good, right? Well, now consider what happens when we define a new variable, by assigning it a value. If you define the variable inside of a function, then it’s in the “local” scope. If you define the variable at the top level of a file, then it’s in the “global” scope. And if you define it in the __builtins__ module (and you really shouldn’t be doing that), then it’ll be in the “builtins” scope. When we defined “list” (e.g., “list = ‘abc'”), we were defining a new variable in the global scope. We didn’t replace or remove the builtin “list” at all! Indeed, the builtin “list” is still available if we use its full name, __builtins__.list:
>>> list = 'abc' >>> type(list) <type 'str'> >>> type(__builtins__.list) <type 'type'>
The problem, then, isn’t that we have replaced the built-in “list”, but rather that we have masked it. Once we have defined “list” in the global scope, all naked references to “list” in that file — as well as in functions defined within that file — will see our global variable “list”, rather than __builtins__.list. How, then, did it help for me to delete “list”? Because del(list) doesn’t delete __builtins__.list. (You can do that, by the way, but that’s for another blog post.) Rather, del(list) in our case deletes “list” from the global scope. When we then ask Python for type(list), it looks in L, E, and G, and doesn’t find anything. It thus goes to __builtins__, finds “list”, and returns us the type of “list”, which is once again a “type” or “class”. Whew! If you enjoyed this, then you might like my three-day, online Python class, which includes such tidbits. The course will begin on January 14th (read more about it at masterpython.com). You can also subscribe to my free, exclusive technology newsletter.