From 7290f5c862517f700993f211e4761b07887df88b Mon Sep 17 00:00:00 2001 From: Sebastian Raschka Date: Tue, 29 Apr 2014 20:06:40 -0400 Subject: [PATCH] legb update --- tutorials/scope_resolution_legb_rule.ipynb | 313 +++++++++++++++++++-- 1 file changed, 291 insertions(+), 22 deletions(-) diff --git a/tutorials/scope_resolution_legb_rule.ipynb b/tutorials/scope_resolution_legb_rule.ipynb index d1370e4..1562be6 100644 --- a/tutorials/scope_resolution_legb_rule.ipynb +++ b/tutorials/scope_resolution_legb_rule.ipynb @@ -34,14 +34,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#Python's scope resolution for variable names and the LEGB rule" + "#A beginner's guide to Python's namespaces, scope resolution, 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 provide short example code blocks that should illustrate the problem followed by short explanations. You can simply read this tutorial from start to end, but I'd like to ecourage you to execute the code snippets - you can either copy & paste them, or for your convenience, simply [download this IPython notebook](https://raw.githubusercontent.com/rasbt/python_reference/master/tutorials/scope_resolution_legb_rule.ipynb)." + "This is a short tutorial about Python's namespaces and the scope resolution for variable names using the LEGB-rule. The following sections will provide short example code blocks that should illustrate the problem followed by short explanations. You can simply read this tutorial from start to end, but I'd like to encourage you to execute the code snippets - you can either copy & paste them, or for your convenience, simply [download this IPython notebook](https://raw.githubusercontent.com/rasbt/python_reference/master/tutorials/scope_resolution_legb_rule.ipynb)." ] }, { @@ -64,7 +64,8 @@ "- [3. LEGB - Local, Enclosed, Global, Built-in](#section_3) \n", "- [Self-assessment exercise](#assessment)\n", "- [Conclusion](#conclusion) \n", - "- [Solutions](#solutions)" + "- [Solutions](#solutions)\n", + "- [Warning: For-loop variables cutting into the global namespace](#for_loop)" ] }, { @@ -105,22 +106,161 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "A namespace represents a mapping from names to objects, and we can access any object in Python by its associated name. E.g., if we create a list object `a_list = [1, 2, 3]`, we can modify this list by using its variable name `a_list`, which maps to the list object stored in the memory.\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", + "### Namespaces" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Roughly speaking, namespaces are just containers for mapping names to objects. As you might have already heard, everything in Python - literals, lists, dictionaries, functions, classes, etc. - is an object. \n", + "Such a \"name-to-object\" mapping allows us to access an object by a name that we've assigned to it. E.g., if we make a simple string assignment via `a_string = \"Hello string\"`, we created a reference to the `\"Hello string\"` object, and henceforth we can access via its variable name `a_string`.\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", + "We can picture a namespace as a Python dictionary structure, where the dictionary keys represent the names and the dictionary values the object itself (and this is also how namespaces are currently implemented in Python), e.g., \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", + "
a_namespace = {'name_a':object_1, 'name_b':object_2, ...}
\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" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, the tricky part is that we have multiple independent namespaces in Python, and names can be reused for different namespaces (only the objects are unique, for example:\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 searches the different namespace levels in the following order L -> E -> G -> B, i.e., if a particular name:object mapping cannot be found in the local namespaces, the namespaces of the enclosed scope are being searched next. If the search in the enclosed scope is unsuccessful, too, Python moves on to the global namespace, and eventually, it will search the global namespaces (side note: if a name cannot found in any of the namespaces, a `NameError` is raised)." + "
a_namespace = {'name_a':object_1, 'name_b':object_2, ...}\n",
+      "b_namespace = {'name_a':object_3, 'name_b':object_4, ...}
\n", + "\n", + "For example, everytime we call a `for-loop` or define a function, it will create its own namespace. Namespaces also have different levels of hierarchy (the so-called \"scope\"), which we will discuss in more detail in the next section." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scope" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the section above, we have learned that namespaces can exist independently from each other and that they are structured in a certain hierarchy, which brings us to the concept of \"scope\". The \"scope\" in Python defines the \"hierarchy level\" in which we search namespaces for certain \"name-to-object\" mappings. \n", + "For example, let us consider the following code:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "i = 1\n", + "\n", + "def foo():\n", + " i = 5\n", + " print(i, 'in foo()')\n", + "\n", + "print(i, 'global')\n", + "\n", + "foo()" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "1 global\n", + "5 in foo()\n" + ] + } + ], + "prompt_number": 4 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here, we just defined the variable name `i` twice, once on the `foo` function." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "- `foo_namespace = {'i':object_3, ...}` \n", + "- `global_namespace = {'i':object_1, 'name_b':object_2, ...}`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "So, how does Python now which namespace it has to search if we want to print the value of the variable `i`? This is where Python's LEGB-rule comes into play, which we will discuss in the next section." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scope resolution for variable names via the LEGB rule." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have seen that multiple namespaces can exist independently from each other and that they can contain the same variable names on different hierachy levels. The \"scope\" defines on which hierarchy level Python searches for a particular \"variable name\" for its associated object. Now, the next question is: \"In which order does Python search the different levels of namespaces before it finds the name-to-object' mapping?\" \n", + "To answer is: It uses the LEGB-rule, which stands for\n", + "\n", + "**Local -> Enclosed -> Global -> Built-in**, \n", + "\n", + "where the arrows should denote the direction of the namespace-hierarchy search order. \n", + "\n", + "- *Local* can be inside a function or class method, for example. \n", + "- *Enclosed* can be its `enclosing` function, e.g., if a function is wrapped inside another function. \n", + "- *Global* refers to the uppermost level of the executing script itself, and \n", + "- *Built-in* are special names that Python reserves for itself. \n", + "\n", + "So, if a particular name:object mapping cannot be found in the local namespaces, the namespaces of the enclosed scope are being searched next. If the search in the enclosed scope is unsuccessful, too, Python moves on to the global namespace, and eventually, it will search the global namespaces (side note: if a name cannot found in any of the namespaces, a *NameError* will is raised).\n", + "\n", + "**Note**: \n", + "Namespaces can also be further nested, for example if we import modules, or if we are defining new classes. In those cases we have to use prefixes to access those nested namespaces. Let me illustrate this concept in the following code block:" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import numpy\n", + "import math\n", + "import scipy\n", + "\n", + "print(math.pi, 'from the math module')\n", + "print(numpy.pi, 'from the numpy package')\n", + "print(scipy.pi, 'from the scipy package')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "3.141592653589793 from the math module\n", + "3.141592653589793 from the numpy package\n", + "3.141592653589793 from the scipy package\n" + ] + } + ], + "prompt_number": 8 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "(This is also why we have to be careful if we import modules via \"`from a_module import *`\", since it loads the variable names into the global namespace and could potentially overwrite already existing variable names)" ] }, { @@ -132,13 +272,6 @@ "
" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "###1. LG - Local vs. Global" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -766,6 +899,142 @@ "metadata": {}, "outputs": [], "prompt_number": 58 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "
\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Warning: For-loop variables cutting into the global namespace" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As it was briefly mentioned in the introduction, `for-loops` will create their own namespaces, which will be deleted after the for-loop has completed. Consider the following example (exectuted in Python 3.4):\n" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for a in range(5):\n", + " if a == 4:\n", + " print(a, '-> a in for-loop')\n", + "print(a, '-> a in global')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "4 -> a in for-loop\n", + "4 -> a in global\n" + ] + } + ], + "prompt_number": 15 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can roughly sketch the situation as follows \n", + "\n", + "*before the for-loop*: \n", + "`- global_namespace = {'a_name':object1, ...}`\n", + "\n", + "*during the for-loop*: \n", + "`- global_namespace = {'a_name':object1, ...}` \n", + "`- for-loop_namespace = {'a':object201, ...}`\n", + "\n", + "*after the for-loop*: \n", + "`- global_namespace = {'a_name':object1, ...}`\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**However, this does not apply if we defined the `for-loop` variable in the global namespace before!**" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "b = 1\n", + "for b in range(5):\n", + " if b == 4:\n", + " print(a, '-> b in for-loop')\n", + "print(b, '-> b in global')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "4 -> b in for-loop\n", + "4 -> b in global\n" + ] + } + ], + "prompt_number": 17 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In **Python 3.4**, we can use closures to prevent the for-loop variable to cut into the global namespace. Here is an example (exectuted in Python 3.4):" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "i = 1\n", + "print([i for i in range(5)])\n", + "print(i, '-> i in global')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "[0, 1, 2, 3, 4]\n", + "1 -> i in global\n" + ] + } + ], + "prompt_number": 14 + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Why did I mention \"Python 3.4\"? Well, as it happens, the same code executed in Python 2.x would print:\n", + "\n", + "
\n",
+      "print(4, '-> i in global')\n",
+      "
"
+     ]
     }
    ],
    "metadata": {}