Python's execution model
In this section, I would like to introduce you to a few very important concepts, such as scope, names, and namespaces. You can read all about Python's execution model in the official language reference, of course, but I would argue that it is quite technical and abstract, so let me give you a less formal explanation first.
Names and namespaces
Say you are looking for a book, so you go to the library and ask someone for the book you want to fetch. They tell you something like Second Floor, Section X, Row Three. So you go up the stairs, look for Section X, and so on.
It would be very different to enter a library where all the books are piled together in random order in one big room. No floors, no sections, no rows, no order. Fetching a book would be extremely hard.
When we write code, we have the same issue: we have to try and organize it so that it will be easy for someone who has no prior knowledge about it to find what they're looking for. When software is structured correctly, it also promotes code reuse. On the other hand, disorganized software is more likely to expose scattered pieces of duplicated logic.
First of all, let's start with the book. We refer to a book by its title and in Python lingo, that would be a name. Python names are the closest abstraction to what other languages call variables. Names basically refer to objects and are introduced by name-binding operations. Let's make a quick example (notice that anything that follows a #
is a comment):
>>> n = 3 # integer number
>>> address = "221b Baker Street, NW1 6XE, London" # Sherlock Holmes' address
>>> employee = {
... 'age': 45,
... 'role': 'CTO',
... 'SSN': 'AB1234567',
... }
>>> # let's print them
>>> n
3
>>> address
'221b Baker Street, NW1 6XE, London'
>>> employee
{'age': 45, 'role': 'CTO', 'SSN': 'AB1234567'}
>>> other_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'other_name' is not defined
We defined three objects in the preceding code (do you remember what are the three features every Python object has?):
- An integer number
n
(type:int
, value:3
) - A string
address
(type:str
, value: Sherlock Holmes' address) - A dictionary
employee
(type:dict
, value: a dictionary that holds three key/value pairs)
Don't worry, I know you're not supposed to know what a dictionary is. We'll see in Chapter 2, Built-in Data Types, that it's the king of Python data structures.
Note
Have you noticed that the prompt changed from >>>
to ...
when I typed in the definition of employee? That's because the definition spans over multiple lines.
So, what are n
, address
, and employee
? They are names. Names that we can use to retrieve data within our code. They need to be kept somewhere so that whenever we need to retrieve those objects, we can use their names to fetch them. We need some space to hold them, hence: namespaces!
A namespace is therefore a mapping from names to objects. Examples are the set of built-in names (containing functions that are always accessible in any Python program), the global names in a module, and the local names in a function. Even the set of attributes of an object can be considered a namespace.
The beauty of namespaces is that they allow you to define and organize your names with clarity, without overlapping or interference. For example, the namespace associated with that book we were looking for in the library can be used to import the book itself, like this:
from library.second_floor.section_x.row_three import book
We start from the library
namespace, and by means of the dot (.
) operator, we walk into that namespace. Within this namespace, we look for second_floor
, and again we walk into it with the .
operator. We then walk into section_x
, and finally within the last namespace, row_three
, we find the name we were looking for: book
.
Walking through a namespace will be clearer when we'll be dealing with real code examples. For now, just keep in mind that namespaces are places where names are associated with objects.
There is another concept, which is closely related to that of a namespace, which I'd like to briefly talk about: the scope.
Scopes
According to Python's documentation:
" A scope is a textual region of a Python program, where a namespace is directly accessible."
Directly accessible means that when you're looking for an unqualified reference to a name, Python tries to find it in the namespace.
Scopes are determined statically, but actually, during runtime, they are used dynamically. This means that by inspecting the source code, you can tell what the scope of an object is, but this doesn't prevent the software from altering that during runtime. There are four different scopes that Python makes accessible (not necessarily all of them are present at the same time, of course):
- The local scope, which is the innermost one and contains the local names.
- The enclosing scope, that is, the scope of any enclosing function. It contains non-local names and also non-global names.
- The global scope contains the global names.
- The built-in scope contains the built-in names. Python comes with a set of functions that you can use in an off-the-shelf fashion, such as
print
,all
,abs
, and so on. They live in the built-in scope.
The rule is the following: when we refer to a name, Python starts looking for it in the current namespace. If the name is not found, Python continues the search to the enclosing scope and this continues until the built-in scope is searched. If a name hasn't been found after searching the built-in scope, then Python raises a NameError
exception, which basically means that the name hasn't been defined (you saw this in the preceding example).
The order in which the namespaces are scanned when looking for a name is therefore: local, enclosing, global, built-in (LEGB).
This is all very theoretical, so let's see an example. In order to show you local and enclosing namespaces, I will have to define a few functions. Don't worry if you are not familiar with their syntax for the moment. We'll study functions in Chapter 4, Functions, the Building Blocks of Code. Just remember that in the following code, when you see def
, it means I'm defining a function:
# scopes1.py # Local versus Global # we define a function, called local def local(): m = 7 print(m) m = 5 print(m) # we call, or `execute` the function local local()
In the preceding example, we define the same name m
, both in the global scope and in the local one (the one defined by the local
function). When we execute this program with the following command (have you activated your virtualenv?):
$ python scopes1.py
We see two numbers printed on the console: 5
and 7
.
What happens is that the Python interpreter parses the file, top to bottom. First, it finds a couple of comment lines, which are skipped, then it parses the definition of the function local
. When called, this function does two things: it sets up a name to an object representing number 7
and prints it. The Python interpreter keeps going and it finds another name binding. This time the binding happens in the global scope and the value is 5
. The next line is a call to the print
function, which is executed (and so we get the first value printed on the console: 5
).
After this, there is a call to the function local
. At this point, Python executes the function, so at this time, the binding m = 7
happens and it's printed.
One very important thing to notice is that the part of the code that belongs to the definition of the local
function is indented by four spaces on the right. Python, in fact, defines scopes by indenting the code. You walk into a scope by indenting, and walk out of it by unindenting. Some coders use two spaces, others three, but the suggested number of spaces to use is four. It's a good measure to maximize readability. We'll talk more about all the conventions you should embrace when writing Python code later.
What would happen if we removed that m = 7
line? Remember the LEGB rule. Python would start looking for m
in the local scope (function local
), and, not finding it, it would go to the next enclosing scope. The next one, in this case, is the global one because there is no enclosing function wrapped around local
. Therefore, we would see two numbers 5
printed on the console. Let's actually see what the code would look like:
# scopes2.py # Local versus Global def local(): # m doesn't belong to the scope defined by the local function # so Python will keep looking into the next enclosing scope. # m is finally found in the global scope print(m, 'printing from the local scope') m = 5 print(m, 'printing from the global scope') local()
Running scopes2.py
will print this:
$ python scopes2.py
5 printing from the global scope
5 printing from the local scope
As expected, Python prints m
the first time, then when the function local
is called, m
isn't found in its scope, so Python looks for it following the LEGB chain until m
is found in the global scope.
Let's see an example with an extra layer, the enclosing scope:
# scopes3.py # Local, Enclosing and Global def enclosing_func(): m = 13 def local(): # m doesn't belong to the scope defined by the local # function so Python will keep looking into the next # enclosing scope. This time m is found in the enclosing # scope print(m, 'printing from the local scope') # calling the function local local() m = 5 print(m, 'printing from the global scope') enclosing_func()
Running scopes3.py
will print on the console:
$ python scopes3.py(5, 'printing from the global scope')(13, 'printing from the local scope')
As you can see, the print
instruction from the function local
is referring to m
as before. m
is still not defined within the function itself, so Python starts walking scopes following the LEGB order. This time m
is found in the enclosing scope.
Don't worry if this is still not perfectly clear for now. It will come to you as we go through the examples in the book. The Classes section of the Python tutorial (https://docs.python.org/3/tutorial/classes.html) has an interesting paragraph about scopes and namespaces. Make sure you read it at some point if you want a deeper understanding of the subject.
Before we finish off this chapter, I would like to talk a bit more about objects. After all, basically everything in Python is an object, so I think they deserve a bit more attention.
Objects and classes
When I introduced objects previously in the A proper introduction section of the chapter, I said that we use them to represent real-life objects. For example, we sell goods of any kind on the web nowadays and we need to be able to handle, store, and represent them properly. But objects are actually so much more than that. Most of what you will ever do, in Python, has to do with manipulating objects.
So, without going into too much detail (we'll do that in later chapters), I want to give you the in a nutshell kind of explanation about classes and objects.
We've already seen that objects are Python's abstraction for data. In fact, everything in Python is an object, infact numbers, strings (data structures that hold text), containers, collections, even functions. You can think of them as if they were boxes with at least three features: an ID (unique), a type, and a value.
But how do they come to life? How do we create them? How do we write our own custom objects? The answer lies in one simple word: classes.
Objects are, in fact, instances of classes. The beauty of Python is that classes are objects themselves, but let's not go down this road. It leads to one of the most advanced concepts of this language: metaclasses. For now, the best way for you to get the difference between classes and objects is by means of an example.
Say a friend tells you, I bought a new bike! You immediately understand what she's talking about. Have you seen the bike? No. Do you know what color it is? Nope. The brand? Nope. Do you know anything about it? Nope. But at the same time, you know everything you need in order to understand what your friend meant when she told you she bought a new bike. You know that a bike has two wheels attached to a frame, a saddle, pedals, handlebars, brakes, and so on. In other words, even if you haven't seen the bike itself, you know the concept of bike. An abstract set of features and characteristics that together form something called bike.
In computer programming, that is called a class. It's that simple. Classes are used to create objects. In fact, objects are said to be instances of classes.
In other words, we all know what a bike is; we know the class. But then I have my own bike, which is an instance of the bike class. And my bike is an object with its own characteristics and methods. You have your own bike. Same class, but different instance. Every bike ever created in the world is an instance of the bike class.
Let's see an example. We will write a class that defines a bike and then we'll create two bikes, one red and one blue. I'll keep the code very simple, but don't fret if you don't understand everything about it; all you need to care about at this moment is to understand the difference between a class and an object (or instance of a class):
# bike.py # let's define the class Bike class Bike: def __init__(self, colour, frame_material): self.colour = colour self.frame_material = frame_material def brake(self): print("Braking!") # let's create a couple of instances red_bike = Bike('Red', 'Carbon fiber') blue_bike = Bike('Blue', 'Steel') # let's inspect the objects we have, instances of the Bike class. print(red_bike.colour) # prints: Red print(red_bike.frame_material) # prints: Carbon fiber print(blue_bike.colour) # prints: Blue print(blue_bike.frame_material) # prints: Steel # let's brake! red_bike.brake() # prints: Braking!
Note
I hope by now I don't need to tell you to run the file every time, right? The filename is indicated in the first line of the code block. Just run $ python filename
, and you'll be fine. But remember to have your virtualenv activated!
So many interesting things to notice here. First things first; the definition of a class happens with the class
statement. Whatever code comes after the class
statement, and is indented, is called the body of the class. In our case, the last line that belongs to the class definition is the print("Braking!")
one.
After having defined the class, we're ready to create instances. You can see that the class body hosts the definition of two methods. A method is basically (and simplistically) a function that belongs to a class.
The first method, __init__
, is an initializer. It uses some Python magic to set up the objects with the values we pass when we create it.
Note
Every method that has leading and trailing double underscores, in Python, is called a magic method. Magic methods are used by Python for a multitude of different purposes; hence it's never a good idea to name a custom method using two leading and trailing underscores. This naming convention is best left to Python.
The other method we defined, brake
, is just an example of an additional method that we could call if we wanted to brake the bike. It contains just a print
statement, of course; it's an example.
We created two bikes then. One has red color and a carbon fiber frame, and the other one has blue color and a steel frame. We pass those values upon creation. After creation, we print out the color property and frame type of the red bike, and the frame type of the blue one just as an example. We also call the brake
method of the red_bike
.
One last thing to notice. You remember I told you that the set of attributes of an object is considered to be a namespace? I hope it's clearer what I meant now. You see that by getting to the frame_type
property through different namespaces (red_bike
, blue_bike
), we obtain different values. No overlapping, no confusion.
The dot (.
) operator is of course the means we use to walk into a namespace, in the case of objects as well.