python_reference/tutorials/scope_resolution_legb_rule.ipynb
2014-04-28 21:42:13 -04:00

644 lines
20 KiB
Plaintext

{
"metadata": {
"name": "scope_resolution_legb_rule"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[Sebastian Raschka](http://www.sebastianraschka.com) \n",
"last updated: 04/28/2014\n",
"\n",
"- [Link to the containing GitHub Repository](https://github.com/rasbt/python_reference)\n",
"- [Link to this IPython Notebook on GitHub](https://github.com/rasbt/pattern_classification/blob/master/python_reference/tutorials/scope_legb_rule.ipynb)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<hr>\n",
"I am really looking forward to your comments and suggestions to improve and extend this tutorial! Just send me a quick note \n",
"via Twitter: [@rasbt](https://twitter.com/rasbt) \n",
"or Email: [bluewoodtree@gmail.com](mailto:bluewoodtree@gmail.com)\n",
"<hr>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#Python's scope resolution for variable names and the LEGB rule"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is a short tutorial about Python's scope resolution for variable names using the LEGB-rule. The following section will have short example code blocks that should illustrate the problem followed by short explanations. You can simply read it from start to end, but I'd recommend you to execute the code snippets yourself by copy & paste, or by directly [downloading this IPython notebook](https://github.com/rasbt/python_reference/raw/master/python_true_false.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"sections\"><a/>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Sections\n",
"- [Introduction to namespaces and scopes](#introduction) \n",
"- [1. LG - Local vs. Global](section_1) \n",
"- [2. LEG - Local, Enclosed, and Global scope](section_2) \n",
"- [3. LEGB - Local, Enclosed, Global, Built-in](section_3) \n",
"- [Conclusion](conclusion) \n",
"- [Solutions](#solutions)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Objectives\n",
"- Namespaces and scopes - Where does Python look for variable names?\n",
"- What happens if the same variable name is defined (reused) multiple times?\n",
"- In which order are namespaces being searched for variable names?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"introduction\"></a>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Introduction to namespaces and scopes"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A namespace represents a mapping from names to objects. E.g., if we want to access any Python object, we do it by the associated name. \n",
"We could picture a namespace as a simple dicitonary, e.g., \n",
"`a_namespace = {'name_a':object_a, 'name_b':object_b, ...}` \n",
"(which is also how namespaces are currently implemented in Python). \n",
"\n",
"Multiple namespaces can use the same names independent from each other. \n",
"In Python, we have separate namespaces for e.g., built-in variables, global variables, different classes, modules, functions, etc. \n",
"A simple example could be: \n",
"\n",
"`global_namespace = {'name_a':object_1, 'name_b':object_2, ...}`, \n",
"`namespace_of_function_b = {'name_a':object_3, 'name_b':object_4, ...}`\n",
"\n",
"For example, let's assume we want to access an object via its name `name_a`, in which namespace would Python look up the mapping?\n",
"\n",
"This is where the concept of **scope** comes into play. In Python, we have 4 different scopes: **L** ocal, **E** nclosed, **G** lobal, **B** uilt-in (LEGB). \n",
"According to the LEGB-rule, Python looks for an object through the namespaces in the following order L -> E -> G -> B: If a variable name is not found in the local namespaces, the namespaces of the enclosed scope are being searched, and if the variable name is also not defined here, the global namespaces come next and so forth."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"###1. LG - Local vs. Global"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Example 1.1** \n",
"As a warm-up exercise, let us first forget about the enclosed (E) and built-in (B) scopes in the LEGB rule and take a look at LG - the local and global scope. \n",
"What does the following code print?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global variable'\n",
"\n",
"def a_func():\n",
" print(a_var, '[ a_var inside a_func() ]')\n",
"\n",
"#a_func()\n",
"#print(a_var, '[ a_var outside a_func() ]')"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**a)**\n",
"<pre>raises an error</pre>\n",
"\n",
"**b)** \n",
"<pre>\n",
"global value [ a_var outside a_func() ]</pre>\n",
"\n",
"**c)** \n",
"<pre>global value [ a_var in a_func() ] \n",
"global value [ a_var outside a_func() ]</pre>\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[go to solution](#solutions)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Here is why:\n",
"\n",
"We call `a_func()` first, which is supposed to print the value of `a_var`. According to the LEGB rule, the function will first look in its own local scope (L) if `a_var` is defined there. Since `a_func()` does not have its own `a_var`, it will look one-level above in the global scope (G) in which we defined `a_var` previously.\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Example 1.2** \n",
"Now, let us define the variable `a_var` in the global and the local scope. \n",
"Can you guess what the following code will produce?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global value'\n",
"\n",
"def a_func():\n",
" a_var = 'local value'\n",
" print(a_var, '[ a_var inside a_func() ]')\n",
"\n",
"#a_func()\n",
"#print(a_var, '[ a_var outside a_func() ]')"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**a)**\n",
"<pre>raises an error</pre>\n",
"\n",
"**b)** \n",
"<pre>local value [ a_var in a_func() ]\n",
"global value [ a_var outside a_func() ]</pre>\n",
"\n",
"**c)** \n",
"<pre>global value [ a_var in a_func() ] \n",
"global value [ a_var outside a_func() ]</pre>\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[go to solution](#solutions)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Here is why:\n",
"\n",
"When we call `a_func()`, it will first look in its local scope (L) for `a_var`, since `a_var` is defined in the local scope of `a_func`, its assigned value `local variable` is printed. Note that this doesn't affect the global variable, which is in a different scope."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"However, it is also possible to modify the global by, e.g., re-assigning a new value to it if we use the global keyword as the following example will illustrate:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global value'\n",
"\n",
"def a_func():\n",
" global a_var\n",
" a_var = 'local value'\n",
" print(a_var, '[ a_var inside a_func() ]')\n",
"\n",
"#print(a_var, '[ a_var outside a_func() ]')\n",
"#a_func()\n",
"#print(a_var, '[ a_var outside a_func() ]')"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But we have to be careful about the order: it is easy to raise an `UnboundLocalError` if we don't explicitly tell Python that we want to use the global scope and try to modify a variable's value (remember, the right side of an assignment operation is executed first):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 1\n",
"\n",
"def a_func():\n",
" a_var = a_var + 1\n",
" print(a_var, '[ a_var inside a_func() ]')\n",
"\n",
"print(a_var, '[ a_var outside a_func() ]')\n",
"a_func()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "UnboundLocalError",
"evalue": "local variable 'a_var' referenced before assignment",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mUnboundLocalError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-29-a433472349aa>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma_var\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'[ a_var outside a_func() ]'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 8\u001b[0;31m \u001b[0ma_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-29-a433472349aa>\u001b[0m in \u001b[0;36ma_func\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0ma_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0ma_var\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma_var\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'[ a_var inside a_func() ]'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'a_var' referenced before assignment"
]
},
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1 [ a_var outside a_func() ]\n"
]
}
],
"prompt_number": 29
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. LEG - Local, Enclosed, and Global scope\n",
"\n",
"\n",
"\n",
"Now, let us introduce the concept of the enclosed (E) scope. Following the order \"Local -> Enclosed -> Global\", can you guess what the following code will print?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Example 2.1**"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global value'\n",
"\n",
"def outer():\n",
" a_var = 'enclosed value'\n",
" \n",
" def inner():\n",
" a_var = 'local value'\n",
" print(a_var)\n",
" \n",
" inner()\n",
"\n",
"#outer()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**a)**\n",
"<pre>global value</pre>\n",
"\n",
"**b)** \n",
"<pre>enclosed value</pre>\n",
"\n",
"**c)** \n",
"<pre>local value</pre>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[go to solution](#solutions)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Here is why:\n",
"\n",
"Let us quickly recapitulate what we just did: We called `outer()`, which defines the variable `a_var` locally (next to an existing `a_var` in the global scope). The `outer()` function then calls `inner()`, which in turn defines a variable with the name `a_var` as well. The `print()` function inside `inner()` looks in the local scope first (L->E), before it goes up the scope hierarchy, and therefore prints the value that was assigned in the local scope."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similar to the concept of the `global` keyword, which we have seen in the section above, we can use the keyword `nonlocal` inside the inner function to explicitely access a variable from the outer (enclosed) scope in order to modify its value:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global value'\n",
"\n",
"def outer():\n",
" a_var = 'local value'\n",
" print('outer before:', a_var)\n",
" def inner():\n",
" nonlocal a_var\n",
" a_var = 'inner value'\n",
" print('in inner():', a_var)\n",
" inner()\n",
" print(\"outer after:\", a_var)\n",
"#outer()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. LEGB - Local, Enclosed, Global, Built-in\n",
"\n",
"To wrap up the LEGB rule, let us come to the built-in scope. Here, we will define our \"own\" length-funcion, which happens to bear the same name as the in-built `len()` function. What outcome do you excpect if we'd execute the following code?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Example 3**"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 'global variable'\n",
"\n",
"\n",
"\n",
"def len(in_var):\n",
" print('called my len() function')\n",
" l = 0\n",
" for i in in_var:\n",
" l += 1\n",
" return l\n",
"\n",
"def a_func(in_var):\n",
" len_in_var = len(in_var)\n",
" print('Input variable is of length', len_in_var)\n",
"\n",
"#a_func('Hello, World!')\n",
" "
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 85
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**a)**\n",
"<pre>raises an error (conflict with in-built `len()` function)</pre>\n",
"\n",
"**b)** \n",
"<pre>called my len() function\n",
"Input variable is of length 13</pre>\n",
"\n",
"**c)** \n",
"<pre>Input variable is of length 13</pre>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[go to solution](#solutions)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Here is why:\n",
"\n",
"Since the exact same names can be used to map names to different objects - as long as the names are in different name spaces - there is no problem of reusing the name `len` to define our own length function (this is just for demonstration pruposes, it is NOT recommended). As we go up in Python's L -> E -> G -> B hierarchy, the function `a_func()` finds `len()` in the global scope first, before it attempts to search the namespaces in the built-in scope."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"conclusion\"\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Conclusion"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I hope this short tutorial was helpful to understand the basic concept of Python's scope resolution order using the LEGB rule. I want to encourage you (as a little self-assessment exercise) to look at the code snippets again tomorrow and check if you can correctly predict all their outcomes."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### A rule of thumb"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In practice, **it is usually a bad idea to modify global variables inside the function scope**, since it often be the cause of confusion and weird errors that are hard to debug. \n",
"If you want to modify a global variable via a function, it is recommended to pass it as an argument and reassign the return-value. \n",
"For example:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"a_var = 2\n",
"\n",
"def a_func(some_var):\n",
" return 2**3\n",
"\n",
"a_var = a_func(a_var)\n",
"print(a_var)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"8\n"
]
}
],
"prompt_number": 42
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name = \"solutions\">\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solutions\n",
"\n",
"In order to prevent you from unintentional spoilers, I have written the solutions in binary format. In order to display the character representation, you just need to execute the following lines of code:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print('Example 1.1:', chr(int('01100011',2)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print('Example 1.2:', chr(int('01100001',2)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 7
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print('Example 2.1:', chr(int('01100011',2)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print('Example 3.1:', chr(int('01100010',2)))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 9
}
],
"metadata": {}
}
]
}