{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[Sebastian Raschka](http://www.sebastianraschka.com) \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/python_reference/blob/master/tutorials/scope_resolution_legb_rule.ipynb)\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%load_ext watermark" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sebastian Raschka 01/27/2016 \n", "\n", "CPython 3.5.1\n", "IPython 4.0.3\n" ] } ], "source": [ "%watermark -a 'Sebastian Raschka' -v -d" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[More information](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/ipython_magic/watermark.ipynb) about the `watermark` magic command extension." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
a_namespace = {'name_a':object_1, 'name_b':object_2, ...}\n", "\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", "
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", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 global\n", "5 in foo()\n" ] } ], "source": [ "i = 1\n", "\n", "def foo():\n", " i = 5\n", " print(i, 'in foo()')\n", "\n", "print(i, 'global')\n", "\n", "foo()" ] }, { "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 know 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": [ "### Tip:\n", "If we want to print out the dictionary mapping of the global and local variables, we can use the\n", "the functions `global()` and `local()`" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "loc in foo(): True\n", "loc in global: False\n", "glob in global: True\n" ] } ], "source": [ "#print(globals()) # prints global namespace\n", "#print(locals()) # prints local namespace\n", "\n", "glob = 1\n", "\n", "def foo():\n", " loc = 5\n", " print('loc in foo():', 'loc' in locals())\n", "\n", "foo()\n", "print('loc in global:', 'loc' in globals()) \n", "print('glob in global:', 'foo' in globals())" ] }, { "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 built-in namespace (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", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.141592653589793 from the math module\n", "3.141592653589793 from the numpy package\n", "3.141592653589793 from the scipy package\n" ] } ], "source": [ "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')" ] }, { "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)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
raises an error\n", "\n", "**b)** \n", "
\n", "global value [ a_var outside a_func() ]\n", "\n", "**c)** \n", "
global value [ a_var inside a_func() ] \n", "global value [ a_var outside a_func() ]\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 define its own `a_var`, it will look one-level above in the global scope (G) in which `a_var` has been defined previously.\n", "
raises an error\n", "\n", "**b)** \n", "
local value [ a_var inside a_func() ]\n", "global value [ a_var outside a_func() ]\n", "\n", "**c)** \n", "
global value [ a_var inside a_func() ] \n", "global value [ a_var outside a_func() ]\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": [ "
global value\n", "\n", "**b)** \n", "
enclosed value\n", "\n", "**c)** \n", "
local value" ] }, { "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 defined the variable `a_var` locally (next to an existing `a_var` in the global scope). Next, the `outer()` function called `inner()`, which in turn defined a variable with of name `a_var` as well. The `print()` function inside `inner()` searched in the local scope first (L->E) before it went up in the scope hierarchy, and therefore it printed 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 explicitly access a variable from the outer (enclosed) scope in order to modify its value. \n", "Note that the `nonlocal` keyword was added in Python 3.x and is not implemented in Python 2.x (yet)." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "outer before: local value\n", "in inner(): inner value\n", "outer after: inner value\n" ] } ], "source": [ "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()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
raises an error (conflict with in-built `len()` function)\n", "\n", "**b)** \n", "
called my len() function\n", "Input variable is of length 13\n", "\n", "**c)** \n", "
Input variable is of length 13" ] }, { "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()` already in the global scope (G) first before it attempts to search the built-in (B) namespace." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", "4 -> i in global\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This goes back to a change that was made in Python 3.x and is described in [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) as follows:\n", "\n", "\"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.0" } }, "nbformat": 4, "nbformat_minor": 0 }