(original) (raw)

\u001b[0m in \u001b[0;36m\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\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[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" ] }, { "name": "stdout", "output_type": "stream", "text": [ "1 [ a_var outside a_func() ]\n" ] } ], "source": [ "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()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", "
" ] }, { "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", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "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()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)**\n", "

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", "
\n", "
" ] }, { "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", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "a_var = 'global variable'\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!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**a)**\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", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Self-assessment exercise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, after we went through a couple of exercises, let us quickly check where we are. So, one more time: What would the following code print out?" ] }, { "cell_type": "code", "execution_count": 59, "metadata": { "collapsed": false }, "outputs": [], "source": [ "a = 'global'\n", "\n", "def outer():\n", " \n", " def len(in_var):\n", " print('called my len() function: ', end=\"\")\n", " l = 0\n", " for i in in_var:\n", " l += 1\n", " return l\n", " \n", " a = 'local'\n", " \n", " def inner():\n", " global len\n", " nonlocal a\n", " a += ' variable'\n", " inner()\n", " print('a is', a)\n", " print(len(a))\n", "\n", "\n", "outer()\n", "\n", "print(len(a))\n", "print('a is', a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[[go to solution](#solutions)]" ] }, { "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", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "8\n" ] } ], "source": [ "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)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "
\n", "
" ] }, { "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", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Example 1.1:', chr(int('01100011',2)))" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Example 1.2:', chr(int('01100010',2)))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Example 2.1:', chr(int('01100011',2)))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print('Example 3.1:', chr(int('01100010',2)))" ] }, { "cell_type": "code", "execution_count": 58, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Execute to run the self-assessment solution\n", "\n", "sol = \"000010100110111101110101011101000110010101110010001010\"\\\n", "\"0000101001001110100000101000001010011000010010000001101001011100110\"\\\n", "\"0100000011011000110111101100011011000010110110000100000011101100110\"\\\n", "\"0001011100100110100101100001011000100110110001100101000010100110001\"\\\n", "\"1011000010110110001101100011001010110010000100000011011010111100100\"\\\n", "\"1000000110110001100101011011100010100000101001001000000110011001110\"\\\n", "\"1010110111001100011011101000110100101101111011011100011101000100000\"\\\n", "\"0011000100110100000010100000101001100111011011000110111101100010011\"\\\n", "\"0000101101100001110100000101000001010001101100000101001100001001000\"\\\n", "\"0001101001011100110010000001100111011011000110111101100010011000010\"\\\n", "\"1101100\"\n", "\n", "sol_str =''.join(chr(int(sol[i:i+8], 2)) for i in range(0, len(sol), 8))\n", "for line in sol_str.split('\\n'):\n", " print(line)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Warning: For-loop variables \"leaking\" into the global namespace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In contrast to some other programming languages, `for-loops` will use the scope they exist in and leave their defined loop-variable behind.\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 -> a in for-loop\n", "4 -> a in global\n" ] } ], "source": [ "for a in range(5):\n", " if a == 4:\n", " print(a, '-> a in for-loop')\n", "print(a, '-> a in global')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**This also applies if we explicitly defined the `for-loop` variable in the global namespace before!** In this case it will rebind the existing variable:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 -> b in for-loop\n", "4 -> b in global\n" ] } ], "source": [ "b = 1\n", "for b in range(5):\n", " if b == 4:\n", " print(b, '-> b in for-loop')\n", "print(b, '-> b in global')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, in **Python 3.x**, 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", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 2, 3, 4]\n", "1 -> i in global\n" ] } ], "source": [ "i = 1\n", "print([i for i in range(5)])\n", "print(i, '-> i in global')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why did I mention \"Python 3.x\"? Well, as it happens, the same code executed in Python 2.x would print:\n", "\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 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 }