"If you want to learn more, please read the [original blog](http://python-history.blogspot.ru/2010/06/method-resolution-order.html) post by Guido van Rossum.\n",
"Here, class `D` searches in `B` first, which in turn inherits from `A` (note that class `C` also inherits from `A`, but has its own `.foo()` method) so that we come up with the search order: `D, B, C, A`. "
"Python `list`s are mutable objects as we all know. So, if we are using the `+=` operator on `list`s, we extend the `list` by directly modifying the object directly. \n",
"\"It often comes as a big surprise for programmers to find (sometimes by way of a hard-to-reproduce bug) that, unlike any other time value, midnight (i.e. `datetime.time(0,0,0)`) is False. A long discussion on the python-ideas mailing list shows that, while surprising, that behavior is desirable\u2014at least in some quarters.\" \n",
"This oddity occurs, because Python keeps an array of small integer objects (i.e., integers between -5 and 256, [see the doc](https://docs.python.org/2/c-api/int.html#PyInt_FromLong))."
"(*I received a comment that this is in fact a CPython artefact and **must not necessarily be true** in all implementations of Python!*)\n",
"\n",
"So the take home message is: always use \"==\" for equality, \"is\" for identity!\n",
"\n",
"Here is a [nice article](http://python.net/%7Egoodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables) explaining it by comparing \"boxes\" (C language) with \"name tags\" (Python)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This example demonstrates that this applies indeed for integers in the range in -5 to 256:"
"If we use the assignment operator to assign one list to another list, we just create a new name reference to the original list. If we want to create a new list object, we have to make a copy of the original list. This can be done via `a_list[:]` or `a_list.copy()`."
"As we have seen above, a shallow copy works fine if we want to create a new list with contents of the original list which we want to modify independently. \n",
"\n",
"However, if we are dealing with compound objects (e.g., lists that contain other lists, [read here](https://docs.python.org/2/library/copy.html) for more information) it becomes a little trickier.\n",
"\n",
"In the case of compound objects, a shallow copy would create a new compound object, but it would just insert the references to the contained objects into the new compound object. In contrast, a deep copy would go \"deeper\" and create also new objects \n",
"for the objects found in the original compound object. \n",
"If you follow the code, the concept should become more clear:"
"- If both values in `or` expressions are `True`, Python will select the first value (e.g., select `\"a\"` in `\"a\" or \"b\"`), and the second one in `and` expressions. \n",
"This is also called **short-circuiting** - we already know that the logical `or` must be `True` if the first value is `True` and therefore can omit the evaluation of the second value.\n",
"Don't use mutable objects (e.g., dictionaries, lists, sets, etc.) as default arguments for functions! You might expect that a new list is created every time when we call the function without providing an argument for the default parameter, but this is not the case: **Python will create the mutable object (default parameter) the first time the function is defined - not when it is called**, see the following code:\n",
"Be aware of what is happening when combining \"`in`\" checks with generators, since they won't evaluate from the beginning once a position is \"consumed\"."
"Chicken or egg? In the history of Python (Python 2.2 to be specific) truth values were implemented via 1 and 0 (similar to the old C). In order to avoid syntax errors in old (but perfectly working) Python code, `bool` was added as a subclass of `int` in Python 2.3.\n",
"Remember the section about the [\"consuming generators\"](consuming_generators)? This example is somewhat related, but the result might still come unexpected. \n",
"In the first example below, we call a `lambda` function in a list comprehension, and the value `i` will be dereferenced every time we call `lambda` within the scope of the list comprehension. Since the list is already constructed when we `for-loop` through the list, it will be set to the last value 4."
"And if you are really keen on using lists, there is a nifty trick that circumvents this problem as a reader nicely pointed out in the comments: We can simply pass the loop variable `i` as a default argument to the lambdas."
"There is nothing particularly surprising about Python's LEGB scope resolution (Local -> Enclosed -> Global -> Built-in), but it is still useful to take a look at some examples!"
"According to the LEGB rule, Python will first look for a variable in the local scope. So if we set the variable `x = 1` `local`ly in the function's scope, it won't have an effect on the `global` `x`."
"If we want to modify the `global` x via a function, we can simply use the `global` keyword to import the variable into the function's scope:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"x = 0\n",
"def in_func():\n",
" global x\n",
" x = 1\n",
" print('in_func:', x)\n",
" \n",
"in_func()\n",
"print('global:', x)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"in_func: 1\n",
"global: 1\n"
]
}
],
"prompt_number": 34
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `local` vs. `enclosed`\n",
"\n",
"Now, let us take a look at `local` vs. `enclosed`. Here, we set the variable `x = 1` in the `outer` function and set `x = 1` in the enclosed function `inner`. Since `inner` looks in the local scope first, it won't modify `outer`'s `x`."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def outer():\n",
" x = 1\n",
" print('outer before:', x)\n",
" def inner():\n",
" x = 2\n",
" print(\"inner:\", x)\n",
" inner()\n",
" print(\"outer after:\", x)\n",
"outer()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"outer before: 1\n",
"inner: 2\n",
"outer after: 1\n"
]
}
],
"prompt_number": 36
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is where the `nonlocal` keyword comes in handy - it allows us to modify the `x` variable in the `enclosed` scope:"
"However, **there are ways** to modify the mutable contents of the tuple without raising the `TypeError`, the solution is the `.extend()` method, or alternatively `.append()` (for lists):"
"**A. Jesse Jiryu Davis** has a nice explanation for this phenomenon (Original source: [http://emptysqua.re/blog/python-increment-is-weird-part-ii/](http://emptysqua.re/blog/python-increment-is-weird-part-ii/))\n",
"If we try to extend the list via `+=` *\"then the statement executes `STORE_SUBSCR`, which calls the C function `PyObject_SetItem`, which checks if the object supports item assignment. In our case the object is a tuple, so `PyObject_SetItem` throws the `TypeError`. Mystery solved.\"*"
"#### One more note about the `immutable` status of tuples. Tuples are famous for being immutable. However, how comes that this code works?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"my_tup = (1,)\n",
"my_tup += (4,)\n",
"my_tup = my_tup + (5,)\n",
"print(my_tup)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"(1, 4, 5)\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"What happens \"behind\" the curtains is that the tuple is not modified, but every time a new object is generated, which will inherit the old \"name tag\":"
"\"List comprehensions are fast, but generators are faster!?\" - No, not really (or significantly, see the benchmarks below). So what's the reason to prefer one over the other?\n",
"- use lists if you want to use the plethora of list methods \n",
"Who has not stumbled across this quote \"we are all consenting adults here\" in the Python community, yet? Unlike in other languages like C++ (sorry, there are many more, but that's one I am most familiar with), we can't really protect class methods from being used outside the class (i.e., by the API user). \n",
"All we can do is to indicate methods as private to make clear that they are better not used outside the class, but it is really up to the class user, since \"we are all consenting adults here\"! \n",
"So, when we want to mark a class method as private, we can put a single underscore in front of it. \n",
"If we additionally want to avoid name clashes with other classes that might use the same method names, we can prefix the name with a double-underscore to invoke the name mangling.\n",
"**The solution** is that we are iterating through the list index by index, and if we remove one of the items in-between, we inevitably mess around with the indexing, look at the following example, and it will become clear:"
"Usually, it is no problem to access global variables in the local scope of a function:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def my_func():\n",
" print(var)\n",
"\n",
"var = 'global'\n",
"my_func()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"global\n"
]
}
],
"prompt_number": 37
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And is also no problem to use the same variable name in the local scope without affecting the local counterpart: "
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def my_func():\n",
" var = 'locally changed'\n",
"\n",
"var = 'global'\n",
"my_func()\n",
"print(var)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"global\n"
]
}
],
"prompt_number": 38
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"But we have to be careful if we use a variable name that occurs in the global scope, and we want to access it in the local function scope if we want to reuse this name:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def my_func():\n",
" print(var) # want to access global variable\n",
" var = 'locally changed' # but Python thinks we forgot to define the local variable!\n",
" \n",
"var = 'global'\n",
"my_func()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "UnboundLocalError",
"evalue": "local variable 'var' referenced before assignment",
"Let's assume a scenario where we want to duplicate sub`list`s of values stored in another list. If we want to create independent sub`list` object, using the arithmetic multiplication operator could lead to rather unexpected (or undesired) results:"
"Very trivial, but this change makes sense, Python 3 now only accepts `print`s with proper parentheses - just like the other function calls ..."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Python 2\n",
">>> print 'Hello, World!'\n",
"Hello, World!\n",
">>> print('Hello, World!')\n",
"Hello, World!\n",
"\n",
"# Python 3\n",
">>> print('Hello, World!')\n",
"Hello, World!\n",
">>> print 'Hello, World!'\n",
" File \"<stdin>\", line 1\n",
" print 'Hello, World!'\n",
" ^\n",
"SyntaxError: invalid syntax"
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And if we want to print the output of 2 consecutive print functions on the same line, you would use a comma in Python 2, and a `end=\"\"` in Python 3:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Python 2\n",
">>> print \"line 1\", ; print 'same line'\n",
"line 1 same line\n",
"\n",
"# Python 3\n",
">>> print(\"line 1\", end=\"\") ; print (\" same line\")\n",
"This is a pretty dangerous thing if you are porting code, or executing Python 3 code in Python 2 since the change in integer-division behavior can often go unnoticed. \n",
"So, I still tend to use a `float(3)/2` or `3/2.0` instead of a `3/2` in my Python 3 scripts to save the Python 2 guys some trouble ... (PS: and vice versa, you can `from __future__ import division` in your Python 2 scripts)."
"`xrange()` was pretty popular in Python 2.x if you wanted to create an iterable object. The behavior was quite similar to a generator ('lazy evaluation'), but you could iterate over it infinitely. The advantage was that it was generally faster than `range()` (e.g., in a for-loop) - not if you had to iterate over the list multiple times, since the generation happens every time from scratch! \n",
"In Python 3, the `range()` was implemented like the `xrange()` function so that a dedicated `xrange()` function does not exist anymore."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# Python 2\n",
"> python -m timeit 'for i in range(1000000):' ' pass'\n",
"10 loops, best of 3: 66 msec per loop\n",
"\n",
" > python -m timeit 'for i in xrange(1000000):' ' pass'\n",
"10 loops, best of 3: 27.8 msec per loop\n",
"\n",
"# Python 3\n",
"> python3 -m timeit 'for i in range(1000000):' ' pass'\n",
"10 loops, best of 3: 51.1 msec per loop\n",
"\n",
"> python3 -m timeit 'for i in xrange(1000000):' ' pass'\n",
"Traceback (most recent call last):\n",
" File \"/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py\", line 292, in main\n",
" x = t.timeit(number)\n",
" File \"/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py\", line 178, in timeit\n",
"Where Python 2 accepts both notations, the 'old' and the 'new' way, Python 3 chokes (and raises a `SyntaxError` in turn) if we don't enclose the exception argument in parentheses:"
"This goes back to a change that was made in Python 3.x and is described in [What\u2019s 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.\""
"Have you ever seen any Python code that used colons inside the parantheses of a function definition?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def foo1(x: 'insert x here', y: 'insert x^2 here'):\n",
" print('Hello, World')\n",
" return"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And what about the fancy arrow here?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def foo2(x, y) -> 'Hi!':\n",
" print('Hello, World')\n",
" return"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Q: Is this valid Python syntax? \n",
"A: Yes!\n",
" \n",
" \n",
"Q: So, what happens if I *just call* the function? \n",
"A: Nothing!\n",
" \n",
"Here is the proof!"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"foo1(1,2)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Hello, World\n"
]
}
],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"foo2(1,2) "
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Hello, World\n"
]
}
],
"prompt_number": 11
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**So, those are function annotations ... ** \n",
"- the colon for the function parameters \n",
"- the arrow for the return value \n",
"\n",
"You probably will never make use of them (or at least very rarely). Usually, we write good function documentations below the function as a docstring - or at least this is how I would do it (okay this case is a little bit extreme, I have to admit):"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def is_palindrome(a):\n",
" \"\"\"\n",
" Case-and punctuation insensitive check if a string is a palindrom.\n",
" \n",
" Keyword arguments:\n",
" a (str): The string to be checked if it is a palindrome.\n",
" \n",
" Returns `True` if input string is a palindrome, else False.\n",
" \n",
" \"\"\"\n",
" stripped_str = [l for l in my_str.lower() if l.isalpha()]\n",
" return stripped_str == stripped_str[::-1]\n",
" "
],
"language": "python",
"metadata": {},
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, function annotations can be useful to indicate that work is still in progress in some cases. But they are optional and I see them very very rarely.\n",
"\n",
"As it is stated in [PEP3107](http://legacy.python.org/dev/peps/pep-3107/#fundamentals-of-function-annotations):\n",
"\n",
"1. Function annotations, both for parameters and return values, are completely optional.\n",
"\n",
"2. Function annotations are nothing more than a way of associating arbitrary Python expressions with various parts of a function at compile-time.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The nice thing about function annotations is their `__annotations__` attribute, which is dictionary of all the parameters and/or the `return` value you annotated."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"foo1.__annotations__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 17,
"text": [
"{'y': 'insert x^2 here', 'x': 'insert x here'}"
]
}
],
"prompt_number": 17
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"foo2.__annotations__"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 18,
"text": [
"{'return': 'Hi!'}"
]
}
],
"prompt_number": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**When are they useful?**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Function annotations can be useful for a couple of things \n",
"Python's `try-except-finally` blocks are very handy for catching and handling errors. The `finally` block is always executed whether an `exception` has been raised or not as illustrated in the following example."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def try_finally1():\n",
" try:\n",
" print('in try:')\n",
" print('do some stuff')\n",
" float('abc')\n",
" except ValueError:\n",
" print('an error occurred')\n",
" else:\n",
" print('no error occurred')\n",
" finally:\n",
" print('always execute finally')\n",
" \n",
"try_finally1()"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"in try:\n",
"do some stuff\n",
"an error occurred\n",
"always execute finally\n"
]
}
],
"prompt_number": 24
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"<br>\n",
"But can you also guess what will be printed in the next code cell?"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def try_finally2():\n",
" try:\n",
" print(\"do some stuff in try block\")\n",
" return \"return from try block\"\n",
" finally:\n",
" print(\"do some stuff in finally block\")\n",
" return \"always execute finally\"\n",
" \n",
"print(try_finally2())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"do some stuff in try block\n",
"do some stuff in finally block\n",
"always execute finally\n"
]
}
],
"prompt_number": 21
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"Here, the abortive `return` statement in the `finally` block simply overrules the `return` in the `try` block, since **`finally` is guaranteed to always be executed.** So, be careful using abortive statements in `finally` blocks!"
"The main reason why we love to use generators in certain cases (i.e., when we are dealing with large numbers of computations) is that it only computes the next value when it is needed, which is also known as \"lazy\" evaluation.\n",
"However, the first clause of an generator is already checked upon it's creation, as the following example demonstrates:"
"Certainly, this is a nice feature, since it notifies us about syntax erros immediately. However, this is (unfortunately) not the case if we have multiple cases in our generator."
"Python has a very convenient \"keyword argument unpacking syntax\" (often also referred to as \"splat\"-operators). This is particularly useful, if we want to define a function that can take a arbitrary number of input arguments."
"Usually, it is the `__init__` method when we think of instanciating a new object from a class. However, it is the static method `__new__` (it is not a class method!) that creates and returns a new instance before `__init__()` is called. \n",
"More specifically, this is what is returned: \n",
"For more information about the `__new__` method, please see the [documentation](https://www.python.org/download/releases/2.2/descrintro/#__new__).\n",
"\n",
"As a little experiment, let us screw with `__new__` so that it returns `None` and see if `__init__` will be executed:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"class a_class(object):\n",
" def __new__(clss, *args, **kwargs):\n",
" print('excecuted __new__')\n",
" return None\n",
" def __init__(self, an_arg):\n",
" print('excecuted __init__')\n",
" self.an_arg = an_arg\n",
" \n",
"a_object = a_class(1)\n",
"print('Type of a_object:', type(a_object))"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"excecuted __new__\n",
"Type of a_object: <class 'NoneType'>\n"
]
}
],
"prompt_number": 53
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As we can see in the code above, `__init__` requires the returned instance from `__new__` in order to called. So, here we just created a `NoneType` object. \n",
"Let us override the `__new__`, now and let us confirm that `__init__` is called now to instantiate the new object\":"
"## Else-clauses: \"conditional else\" and \"completion else\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I would claim that the conditional \"else\" is every programmer's daily bread and butter. However, there is a second flavor of \"else\"-clauses in Python, which I will call \"completion else\" (for reason that will become clear later). \n",
"Why am I showing those simple examples? I think they are good to highlight some of the key points: It is **either** the code under the `if` clause that is executed, **or** the code under the `else` block, but not both. \n",
"If the condition of the `if` clause evaluates to `True`, the `if`-block is exectured, and if it evaluated to `False`, it is the `else` block. \n",
"\n",
"### Completion else\n",
"**In contrast** to the **either...or*** situation that we know from the conditional `else`, the completion `else` is executed if a code block finished. \n",
"To show you an example, let us use `else` for error-handling:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Completion else (try-except)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"try:\n",
" print('first element:', a_list[0])\n",
"except IndexError:\n",
" print('raised IndexError')\n",
"else:\n",
" print('no error in try-block')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"first element: 1\n",
"no error in try-block\n"
]
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"try:\n",
" print('third element:', a_list[2])\n",
"except IndexError:\n",
" print('raised IndexError')\n",
"else:\n",
" print('no error in try-block')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"raised IndexError\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"In the code above, we can see that the code under the **`else`-clause is only executed if the `try-block` was executed without encountering an error, i.e., if the `try`-block is \"complete\".** \n",
"The same rule applies to the \"completion\" `else` in while- and for-loops, which you can confirm in the following samples below."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Completion else (while-loop)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"i = 0\n",
"while i < 2:\n",
" print(i)\n",
" i += 1\n",
"else:\n",
" print('in else')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"0\n",
"1\n",
"in else\n"
]
}
],
"prompt_number": 7
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"i = 0\n",
"while i < 2:\n",
" print(i)\n",
" i += 1\n",
" break\n",
"else:\n",
" print('completed while-loop')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"0\n"
]
}
],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Completion else (for-loop)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for i in range(2):\n",
" print(i)\n",
"else:\n",
" print('completed for-loop')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"0\n",
"1\n",
"completed for-loop\n"
]
}
],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"for i in range(2):\n",
" print(i)\n",
" break\n",
"else:\n",
" print('completed for-loop')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"0\n"
]
}
],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a name=\"string_interning\"></a>\n",
"<br>\n",
"<br>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Interning of compile-time constants vs. run-time expressions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[[back to top](#sections)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This might not be particularly useful, but it is nonetheless interesting: Python's interpreter is interning compile-time constants but not run-time expressions (note that this is implementation-specific).\n",
"Let us have a look at the simple example below. Here we are creating 3 variables and assign the value \"Hello\" to them in different ways before we test them for identity."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"hello1 = 'Hello'\n",
"\n",
"hello2 = 'Hell' + 'o'\n",
"\n",
"hello3 = 'Hell'\n",
"hello3 = hello3 + 'o'\n",
"\n",
"print('hello1 is hello2:', hello1 is hello2)\n",
"print('hello1 is hello3:', hello1 is hello3)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"hello1 is hello2: True\n",
"hello1 is hello3: False\n"
]
}
],
"prompt_number": 34
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, how does it come that the first expression evaluates to true, but the second does not? To answer this question, we need to take a closer look at the underlying byte codes:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import dis\n",
"def hello1_func():\n",
" s = 'Hello'\n",
" return s\n",
"dis.dis(hello1_func)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
" 3 0 LOAD_CONST 1 ('Hello')\n",
" 3 STORE_FAST 0 (s)\n",
"\n",
" 4 6 LOAD_FAST 0 (s)\n",
" 9 RETURN_VALUE\n"
]
}
],
"prompt_number": 38
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def hello2_func():\n",
" s = 'Hell' + 'o'\n",
" return s\n",
"dis.dis(hello2_func)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
" 2 0 LOAD_CONST 3 ('Hello')\n",
" 3 STORE_FAST 0 (s)\n",
"\n",
" 3 6 LOAD_FAST 0 (s)\n",
" 9 RETURN_VALUE\n"
]
}
],
"prompt_number": 39
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def hello3_func():\n",
" s = 'Hell'\n",
" s = s + 'o'\n",
" return s\n",
"dis.dis(hello3_func)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
" 2 0 LOAD_CONST 1 ('Hell')\n",
" 3 STORE_FAST 0 (s)\n",
"\n",
" 3 6 LOAD_FAST 0 (s)\n",
" 9 LOAD_CONST 2 ('o')\n",
" 12 BINARY_ADD\n",
" 13 STORE_FAST 0 (s)\n",
"\n",
" 4 16 LOAD_FAST 0 (s)\n",
" 19 RETURN_VALUE\n"
]
}
],
"prompt_number": 40
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<br>\n",
"It looks like that `'Hello'` and `'Hell'` + `'o'` are both evaluated and stored as `'Hello'` at compile-time, whereas the third version \n",
"`s = 'Hell'` \n",
"`s = s + 'o'` seems to be not interned. Let us quickly confirm the behavior with the following code:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print(hello1_func() is hello2_func())\n",
"print(hello1_func() is hello3_func())"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"True\n",
"False\n"
]
}
],
"prompt_number": 42
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, to show that this hypothesis is the answer to this rather unexpected observation, let us `intern` the value manually:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import sys\n",
"\n",
"print(hello1_func() is sys.intern(hello3_func()))"