4. Intermezzo: Glossary
This writeup makes use of several esoteric Python terms and functions; a good understanding of these terms is integral to gaining a better, and more in-depth understanding of Python. A description of some of these terms and functions follows in sections below.
4.1 Names and Binding
In Python, objects are referenced by names. names are analogous to variables in C++ and Java.
1 >>> x = 5
In the above, example, x is a name that references the object, 5. The process of assigning a reference to 5 to x is called binding.
A name binding causes a name to be associated with an object in the innermost scope of the program. Bindings may occur during instances, such as variable assignment or function or method call when function parameters are bound to the arguments. It is important to note that names are just symbols and they have no type associated with them; names are only references to objects that have types
4.2 Code Blocks
A code block is a piece of program code that executes as a single unit in Python. Modules, functions and classes are all examples of code blocks. Commands typed in interactively at the REPL, script commands run with the -c option are also code blocks. A code block has several namespaces associated with it. For example, a module code block has access to the global namespace while a function code block has access to the local as well as the global namespaces.
4.3 Name-spaces
A * namespace* is a context in which a given set of names is bound to objects. The Python interpreter uses dictionary mappings to represents namespaces. The built-in namespace is one example of a namespace; it contains all the built-in functions, and it can be accessed using __builtins__.__dict__.
The interpreter has access to multiple namespaces including the global namespace, the built-in namespace and the local namespace. These namespaces are created at different times and have different lifetimes. For example, whenever a function executes, it creates a new local namespace that it discards when it exits or returns. The interpreter creates the global namespace at the start of the execution of a module and all names defined in this namespace are available module-wide. The built-in namespace comes into existence when the interpreter is invoked and contains all the built-in names. These three namespaces are the main namespace available to the interpreter.
4.4 Scopes
A scope is an area of a program in which a set of name bindings (namespaces) is visible and directly accessible. Direct access is an essential characteristic of a scope as will be explained when classes are discussed. Direct access means that a name, name, can be used as-is, without the need for dot notation such as SomeClassOrModule.name to access it. At runtime, the following scopes may be available.
- Innermost scope with local names,
- The scope of enclosing functions if any (this is applicable for nested functions),
- The current module’s globals scope,
- The scope containing the builtin namespace.
When the interpreter encounters a name, it searches the scopes’ namespaces in ascending order as listed above and if it does not find the name, it raises an exception. Python supports static scoping, also known as lexical scoping; this means that the visibility of a set of name bindings can be inferred by only inspecting the program text.
Note
Python has a quirky scoping rule that prevents a reference to an object in the global scope from being modified in a local scope; such an attempt will throw an UnboundLocalError exception. To modify an object from the global scope within a local scope, the global keyword is used with the object name before we attempt to modify it. Listing 3.1 illustrates this.
1 >>> a = 1
2 >>> def inc_a(): a += 2
3 ...
4 >>> inc_a()
5 Traceback (most recent call last):
6 File "<stdin>", line 1, in <module>
7 File "<stdin>", line 1, in inc_a
8 UnboundLocalError: local variable 'a' referenced before assignment
9
10 >>> a = 1
11 >>> def inc_a():
12 ... global a
13 ... a += 1
14 ...
15 >>> inc_a()
16 >>> a
17 2
Python also has the nonlocal keyword for when we need to modify a variable bound in an outer non-global scope from an inner scope. This functionality proves very handy when working with nested functions (also referred to as closures). Listing 3.2 is a trivial illustration of the nonlocal keyword in a counter object.
1 >>> def make_counter():
2 ... count = 0
3 ... def counter():
4 ... nonlocal count # nonlocal captures the count binding from enclosing scope not global scope
5 ... count += 1
6 ... return count
7 ... return counter
8 ...
9 >>> counter_1 = make_counter()
10 >>> counter_2 = make_counter()
11 >>> counter_1()
12 1
13 >>> counter_1()
14 2
15 >>> counter_2()
16 1
17 >>> counter_2()
18 2
4.5 eval()
eval is a built-in method for dynamically executing Python expressions in a string (the content of the string must be a valid python expression) or code objects. The function has the following signature eval(expression, globals=None, locals=None). If supplied, the globals argument to the eval function must be a dictionary while the locals argument can be any mapping. The evaluation of the supplied expression occurs using the globals and locals dictionaries as the global and local namespaces. If the __builtins__ is absent from the globals dictionary, the interpreter copies the current globals namespace into the globals variable before it parses the expression. This way it is possible to restrict or sandbox the execution environment by restricting access to the standard builtins. eval when called returns the result of executing the expression or code object for example:
1 >>> eval("2 + 1") # note the expression is in a string
2 3
Since eval can take arbitrary code objects as an argument and return the value of executing such expressions, it along with exec, is used in executing arbitrary Python code that has been compiled into code objects using the compile method. Online Python interpreters can execute Python code supplied by their users using both eval and exec among other techniques.
4.6 exec()
exec is the counterpart to eval. This executes a string interpreted as a suite of Python statements or a code object. The code supplied is supposed to be valid as a file input in both cases. exec has the following signature: exec(object[, globals[, locals]]). Listing 3.4 shows an example of the use of exec.
1 # the acct.py file is located somewhere on file
2 >>> cont = open('acct.py', 'r').read()
3 >>> cont
4 'class Account:\n """base class for representing user accounts"""\n num_accounts = 0\n\n def __in\
5 it__(self, name, balance):\n self.name = name \n self.balance = balance \n Account.nu\
6 m_accounts += 1\n\n def del_account(self):\n Account.num_accounts -= 1\n\n def __getattr__(se\
7 lf, name):\n """handle attribute reference for non-existent attribute"""\n return "Hey I don\
8 t see any attribute called {}".format(name)\n\n def deposit(self, amt):\n self.balance = self.ba\
9 lance + amt \n\n def withdraw(self, amt):\n self.balance = self.balance - amt \n\n def inquir\
10 y(self):\n return "Name={}, balance={}".format(self.name, self.balance) \n\n'
11 >>> exec(cont)
12 # exec content of file using the default name-spaces
13 >>> Account # we can now reference the account class
14 <class '__main__.Account'>
15 >>>
If we omit the optional arguments, the interpreter executes the code in the current scope. If we provide only the globals argument, it must be a dictionary, and the interpreter uses that value for the global and the local variables. If provided, the locals argument can be any mapping object. If the supplied globals dictionary does not contain a value for the key __builtins__, a reference to the dictionary of the built-in module builtins is inserted under that key. One can control the builtins that are available to the executed code by inserting a custom __builtins__ dictionary into globals before passing it to exec() thus creating a sandbox.