Skip to content

Instantly share code, notes, and snippets.

@Sami3rdm
Created October 2, 2019 01:55
Show Gist options
  • Save Sami3rdm/9291e13cc3893ebefbb8bfcdc343ca52 to your computer and use it in GitHub Desktop.
Save Sami3rdm/9291e13cc3893ebefbb8bfcdc343ca52 to your computer and use it in GitHub Desktop.
lab1
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Lab 1b (optional): Functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview\n",
"Build familiarity with reading and writing Python functions with different types of formal parameters, explore some nuances of function execution semantics, and dive into the internals of functions.\n",
"\n",
"*Disclaimer: we know that this lab is particularly focused on Python semantics, which may not seem exciting at first. However, mastering the mechanics of Python functions gives you access to a whole lot of powerful tools that either don't exist or are uncommon or hard-to-use in other languages! The skills you learn through this lab will allow you to write (and debug) powerful Pythonic code quickly and easily!*\n",
"\n",
"**As with Lab 2, we don't expect you to finish all of the material here in one class period. If you do - great! But if not, you are encouraged to work through the extra material at your own pace - it explores interesting and intriguing aspects of Python functions.**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Review\n",
"\n",
"As always, take a moment to read through the slides from this week at the [course website](https://stanfordpython.com/#lecture). In particular, pay attention the quick overview of best practices in Python style mechanics."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exploring Arguments and Parameters\n",
"\n",
"With a partner, work through the following problems."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Consider the following function definition:\n",
"\n",
"```Python\n",
"def print_two(a, b):\n",
" print(\"Arguments: {0} and {1}\".format(a, b))\n",
"```\n",
"\n",
"For each of the following function calls, predict whether the call is valid or not. If it is valid, what will the output be? If it is invalid, what is the cause of the error?\n",
"\n",
"*Note: make your predictions **before** running the code interactively. Then check yourself!*\n",
"\n",
"```Python\n",
"# Valid or invalid?\n",
"print_two()\n",
"print_two(4, 1)\n",
"print_two(41)\n",
"print_two(a=4, 1)\n",
"print_two(4, a=1)\n",
"print_two(4, 1, 1)\n",
"print_two(b=4, 1)\n",
"print_two(a=4, b=1)\n",
"print_two(b=1, a=4)\n",
"print_two(1, a=1)\n",
"print_two(4, 1, b=1)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Before running me, predict which of these calls will be invalid and which will be valid!\n",
"# For valid calls, what is the output?\n",
"# For invalid calls, why is it invalid?\n",
"def print_two(a, b):\n",
" print(\"Arguments: {0} and {1}\".format(a, b))\n",
"\n",
"# Uncomment the ones you want to run!\n",
"# print_two()\n",
"# print_two(4, 1)\n",
"# print_two(41)\n",
"# print_two(a=4, 1)\n",
"# print_two(4, a=1)\n",
"# print_two(4, 1, 1)\n",
"# print_two(b=4, 1)\n",
"# print_two(a=4, b=1)\n",
"# print_two(b=1, a=4)\n",
"# print_two(1, a=1)\n",
"# print_two(4, 1, b=1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"Write at least two more instances of function calls, not listed above, and predict their output. Are they valid or invalid? Check your hypothesis.\n",
"\n",
"*These \"write-some-more\" problems are your chance to clarify your own understanding of function call semantics. You can skip them if you'd like, but using the interactive interpreter to test your own hypotheses is a crucial Python skill that lets you answer questions of the form \"But what happens if I...\"*"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Write two more function calls.\n",
"# print_two(...)\n",
"# print_two(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Default Arguments\n",
"\n",
"Consider the following function definition:\n",
"\n",
"```Python\n",
"def keyword_args(a, b=1, c='X', d=None):\n",
" print(\"a:\", a)\n",
" print(\"b:\", b)\n",
" print(\"c:\", c)\n",
" print(\"d:\", d)\n",
"```\n",
"\n",
"For each of the following function calls, predict whether the call is valid or not. If it is valid, what will the output be? If it is invalid, what is the cause of the error?\n",
"\n",
"```Python\n",
"keyword_args(5)\n",
"keyword_args(a=5)\n",
"keyword_args(5, 8)\n",
"keyword_args(5, 2, c=4)\n",
"keyword_args(5, 0, 1)\n",
"keyword_args(5, 2, d=8, c=4)\n",
"keyword_args(5, 2, 0, 1, \"\")\n",
"keyword_args(c=7, 1)\n",
"keyword_args(c=7, a=1)\n",
"keyword_args(5, 2, [], 5)\n",
"keyword_args(1, 7, e=6)\n",
"keyword_args(1, c=7)\n",
"keyword_args(5, 2, b=4)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Before running me, predict which of these calls will be invalid and which will be valid!\n",
"# For valid calls, what is the output?\n",
"# For invalid calls, why is it invalid?\n",
"def keyword_args(a, b=1, c='X', d=None):\n",
" print(\"a:\", a)\n",
" print(\"b:\", b)\n",
" print(\"c:\", c)\n",
" print(\"d:\", d)\n",
" \n",
"# Uncomment the ones you want to run!\n",
"# keyword_args(5)\n",
"# keyword_args(a=5)\n",
"# keyword_args(5, 8)\n",
"# keyword_args(5, 2, c=4)\n",
"# keyword_args(5, 0, 1)\n",
"# keyword_args(5, 2, d=8, c=4)\n",
"# keyword_args(5, 2, 0, 1, \"\")\n",
"# keyword_args(c=7, 1)\n",
"# keyword_args(c=7, a=1)\n",
"# keyword_args(5, 2, [], 5)\n",
"# keyword_args(1, 7, e=6)\n",
"# keyword_args(1, c=7)\n",
"# keyword_args(5, 2, b=4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Write at least two more instances of function calls, not listed above, and predict their output. Are they valid or invalid? Check your hypothesis."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Write two more function calls.\n",
"# keyword_args(...)\n",
"# keyword_args(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Exploring Variadic Argument lists\n",
"As before, consider the following function definition: \n",
"\n",
"```Python\n",
"def variadic(*args, **kwargs):\n",
" print(\"Positional:\", args)\n",
" print(\"Keyword:\", kwargs)\n",
"```\n",
"\n",
"For each of the following function calls, predict whether the call is valid or not. If it is valid, what will the output be? If it is invalid, what is the cause of the error?\n",
"\n",
"```Python\n",
"variadic(2, 3, 5, 7)\n",
"variadic(1, 1, n=1)\n",
"variadic(n=1, 2, 3)\n",
"variadic()\n",
"variadic(cs=\"Computer Science\", pd=\"Product Design\")\n",
"variadic(cs=\"Computer Science\", cs=\"CompSci\", cs=\"CS\")\n",
"variadic(5, 8, k=1, swap=2)\n",
"variadic(8, *[3, 4, 5], k=1, **{'a':5, 'b':'x'})\n",
"variadic(*[8, 3], *[4, 5], k=1, **{'a':5, 'b':'x'})\n",
"variadic(*[3, 4, 5], 8, *(4, 1), k=1, **{'a':5, 'b':'x'})\n",
"variadic({'a':5, 'b':'x'}, *{'a':5, 'b':'x'}, **{'a':5, 'b':'x'})\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Before running me, predict which of these calls will be invalid and which will be valid!\n",
"# For valid calls, what is the output?\n",
"# For invalid calls, why is it invalid?\n",
"def variadic(*args, **kwargs):\n",
" print(\"Positional:\", args)\n",
" print(\"Keyword:\", kwargs)\n",
"\n",
"# Uncomment the ones you want to run!\n",
"# variadic(2, 3, 5, 7)\n",
"# variadic(1, 1, n=1)\n",
"# variadic(n=1, 2, 3)\n",
"# variadic()\n",
"# variadic(cs=\"Computer Science\", pd=\"Product Design\")\n",
"# variadic(cs=\"Computer Science\", cs=\"CompSci\", cs=\"CS\")\n",
"# variadic(5, 8, k=1, swap=2)\n",
"# variadic(8, *[3, 4, 5], k=1, **{'a':5, 'b':'x'})\n",
"# variadic(*[8, 3], *[4, 5], k=1, **{'a':5, 'b':'x'})\n",
"# variadic(*[3, 4, 5], 8, *(4, 1), k=1, **{'a':5, 'b':'x'})\n",
"# variadic({'a':5, 'b':'x'}, *{'a':5, 'b':'x'}, **{'a':5, 'b':'x'})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Write at least two more instances of function calls, not listed above, and predict their output. Are they valid or invalid? Check your hypothesis."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Write two more function calls.\n",
"# variadic(...)\n",
"# variadic(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### *Optional: Putting it all together*\n",
"*If you feel confident that you understand how function calling works, you can skip this section. We suggest that you work through it if you'd like more practice, but the final decision is up to you.*\n",
"\n",
"Often, however, we don't just see keyword arguments of variadic parameter lists in isolated situations. The following function definition, which incorporates positional parameters, keyword parameters, variadic positional parameters, keyword-only default parameters and variadic keyword parameters, is valid Python code. \n",
"\n",
"```Python\n",
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options):\n",
" print(\"x:\", x)\n",
" print(\"y:\", y)\n",
" print(\"z:\", z)\n",
" print(\"nums:\", nums)\n",
" print(\"indent:\", indent)\n",
" print(\"spaces:\", spaces)\n",
" print(\"options:\", options)\n",
"```\n",
"\n",
"For each of the following function calls, predict whether the call is valid or not. If it is valid, what will the output be? If it is invalid, what is the cause of the error?\n",
"\n",
"```Python\n",
"all_together(2)\n",
"all_together(2, 5, 7, 8, indent=False)\n",
"all_together(2, 5, 7, 6, indent=None)\n",
"all_together()\n",
"all_together(indent=True, 3, 4, 5)\n",
"all_together(**{'indent': False}, scope='maximum')\n",
"all_together(dict(x=0, y=1), *range(10))\n",
"all_together(**dict(x=0, y=1), *range(10))\n",
"all_together(*range(10), **dict(x=0, y=1))\n",
"all_together([1, 2], {3:4})\n",
"all_together(8, 9, 10, *[2, 4, 6], x=7, spaces=0, **{'a':5, 'b':'x'})\n",
"all_together(8, 9, 10, *[2, 4, 6], spaces=0, **{'a':[4,5], 'b':'x'})\n",
"all_together(8, 9, *[2, 4, 6], *dict(z=1), spaces=0, **{'a':[4,5], 'b':'x'})\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Before running me, predict which of these calls will be invalid and which will be valid!\n",
"# For valid calls, what is the output?\n",
"# For invalid calls, why is it invalid?\n",
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options):\n",
" print(\"x:\", x)\n",
" print(\"y:\", y)\n",
" print(\"z:\", z)\n",
" print(\"nums:\", nums)\n",
" print(\"indent:\", indent)\n",
" print(\"spaces:\", spaces)\n",
" print(\"options:\", options)\n",
" \n",
"# Uncomment the ones you want to run!\n",
"# all_together(2)\n",
"# all_together(2, 5, 7, 8, indent=False)\n",
"# all_together(2, 5, 7, 6, indent=None)\n",
"# all_together()\n",
"# all_together(indent=True, 3, 4, 5)\n",
"# all_together(**{'indent': False}, scope='maximum')\n",
"# all_together(dict(x=0, y=1), *range(10))\n",
"# all_together(**dict(x=0, y=1), *range(10))\n",
"# all_together(*range(10), **dict(x=0, y=1))\n",
"# all_together([1, 2], {3:4})\n",
"# all_together(8, 9, 10, *[2, 4, 6], x=7, spaces=0, **{'a':5, 'b':'x'})\n",
"# all_together(8, 9, 10, *[2, 4, 6], spaces=0, **{'a':[4,5], 'b':'x'})\n",
"# all_together(8, 9, *[2, 4, 6], *dict(z=1), spaces=0, **{'a':[4,5], 'b':'x'})"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Write at least two more instances of function calls, not listed above, and predict their output. Are they valid or invalid? Check your hypothesis."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Write two more function calls.\n",
"# all_together(...)\n",
"# all_together(...)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Writing Functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `speak_excitedly`\n",
"Write a function `speak_excitedly` that accepts one required positional argument (a message) and two optional keyword arguments, the first of which is a positive integer referring to the number of exclamation marks to put at the end of the message (defaulting to `1`), and the second of which is a boolean flag indicating whether or not to capitalize the message (defaulting to `False`).\n",
"\n",
"What would the function signature and implementation look like for this function?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def speak_excitedly(???):\n",
" \"\"\"Print a message, with an optional number of exclamation points and optional capitalization.\"\"\"\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"How would you call this function to produce the following outputs?\n",
"\n",
"```Python\n",
"\"I love Python!\"\n",
"\"Keyword arguments are great!!!!\"\n",
"\"I guess Java is okay...\"\n",
"\"LET'S GO STANFORD!!\"\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"speak_excitedly(???) # => \"I love Python!\"\n",
"speak_excitedly(???) # => \"Keyword arguments are great!!!!\"\n",
"speak_excitedly(???) # => \"I guess Java is okay...\"\n",
"speak_excitedly(???) # => \"LET'S GO STANFORD!!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `average`\n",
"Write a function `average` that accepts a variable number of integer positional arguments and computes the average. If no arguments are supplied, the function should return `None`.\n",
"\n",
"What would the function signature and implementation look like for this function?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def average(???):\n",
" \"\"\"Return the average of numeric arguments or None if no arguments are supplied.\"\"\"\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It should be possible to call the function as follows:\n",
"\n",
"```Python\n",
"average() # => None\n",
"average(5) # => 5.0\n",
"average(6, 8, 9, 11) # => 8.5\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(average()) # => None\n",
"print(average(5)) # => 5.0\n",
"print(average(6, 8, 9, 11)) # => 8.5"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Suppose that we have a list `l = [???]` supplied by the user (or some file!) of unknown contents. How can we use the `average` function we just wrote function to compute the average of this list? For this part of the problem, do not use the builtin `sum` or `len` functions – try unpacking the contents of `l` into `average`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"l = [3, 1, 41, 592, 65358] # or any other user-defined input.\n",
"\n",
"print(average(???))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Challenge: `make_table`\n",
"\n",
"Write a function to make a table out of an arbitrary number of keyword arguments. For example, \n",
"\n",
"```Python\n",
"make_table(\n",
" first_name=\"Sam\",\n",
" last_name=\"Redmond\",\n",
" shirt_color=\"pink\"\n",
")\n",
"```\n",
"\n",
"should produce\n",
"\n",
"```\n",
"=========================\n",
"| first_name | Sam |\n",
"| last_name | Redmond |\n",
"| shirt_color | pink |\n",
"=========================\n",
"```\n",
"\n",
"Additionally, there should be two parameters, `key_justify` and `value_justify`, whose default values are `'left'` and `'right'` respectively. These keyword arguments will control the text alignment for keys and values in the table. Valid options for these parameters are `['left', 'right', 'center']`. There should be an extra space of padding on either side of the keys and values. As another example,\n",
"\n",
"```Python\n",
"make_table(\n",
" key_justify=\"right\",\n",
" value_justify=\"center\",\n",
" song=\"Style\",\n",
" artist_fullname=\"Taylor $wift\",\n",
" album=\"1989\"\n",
")\n",
"```\n",
"\n",
"should produce\n",
"\n",
"```\n",
"==================================\n",
"| song | Style |\n",
"| artist_fullname | Taylor $wift |\n",
"| album | 1989 |\n",
"==================================\n",
"```\n",
"\n",
"What would the function signature and implementation look like for this function?\n",
"\n",
"```\n",
"def make_table(???):\n",
" pass\n",
"```\n",
"\n",
"Hint: you may find Python's string `.format()` [alignment specifiers](https://pyformat.info/#string_pad_align) useful."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def make_table(???):\n",
" pass"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Function Nuances"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Return\n",
"\n",
"Predict the output of the following code snippet. Then, run the code to check your hypothesis.\n",
"\n",
"```Python\n",
"def say_hello():\n",
" print(\"Hello!\")\n",
"\n",
"print(say_hello()) # => ?\n",
"\n",
"def echo(arg=None):\n",
" print(\"arg:\", arg)\n",
" return arg\n",
"\n",
"print(echo()) # => ?\n",
"print(echo(5)) # => ?\n",
"print(echo(\"Hello\")) # => ?\n",
"\n",
"def drive(has_car):\n",
" if not has_car:\n",
" # Please never actually signal an error like this...\n",
" return \"Oh no!\"\n",
" return 100 # miles\n",
"\n",
"print(drive(False)) # => ?\n",
"print(drive(True)) # => ?\n",
"```\n",
"\n",
"If you made any incorrect predictions, talk to a partner about why!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def say_hello():\n",
" print(\"Hello!\")\n",
"\n",
"print(say_hello()) # => ?\n",
"\n",
"def echo(arg=None):\n",
" print(\"arg:\", arg)\n",
" return arg\n",
"\n",
"print(echo()) # => ?\n",
"print(echo(5)) # => ?\n",
"print(echo(\"Hello\")) # => ?\n",
"\n",
"def drive(has_car):\n",
" if not has_car:\n",
" # Please never actually signal an error like this...\n",
" return \"Oh no!\"\n",
" return 100 # miles\n",
"\n",
"print(drive(False)) # => ?\n",
"print(drive(True)) # => ?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Parameters and Object Reference\n",
"\n",
"*Optional Reading: [Jeff Knupp's Blog](https://jeffknupp.com/blog/2012/11/13/is-python-callbyvalue-or-callbyreference-neither/)*\n",
"\n",
"Suppose we have the following two functions:\n",
"\n",
"```Python\n",
"def reassign(arr):\n",
" arr = [4, 1]\n",
" print(\"Inside reassign: arr = {}\".format(arr))\n",
"\n",
"def append_one(arr):\n",
" arr.append(1) \n",
" print(\"Inside append_one: arr = {}\".format(arr))\n",
"```\n",
"\n",
"Predict what the following code snippet will output. What's the difference between the sections? What is the cause of this difference?\n",
"\n",
"```Python\n",
"l = [4]\n",
"print(\"Before reassign: arr={}\".format(l)) # => ?\n",
"reassign(l)\n",
"print(\"After reassign: arr={}\".format(l)) # => ?\n",
"\n",
"l = [4]\n",
"print(\"Before append_one: arr={}\".format(l)) # => ?\n",
"append_one(l)\n",
"print(\"After append_one: arr={}\".format(l)) # => ?\n",
"```\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def reassign(arr):\n",
" arr = [4, 1]\n",
" print(\"Inside reassign: arr = {}\".format(arr))\n",
"\n",
"def append_one(arr):\n",
" arr.append(1) \n",
" print(\"Inside append_one: arr = {}\".format(arr))\n",
" \n",
"l = [4]\n",
"print(\"Before reassign: arr={}\".format(l)) # => ?\n",
"reassign(l)\n",
"print(\"After reassign: arr={}\".format(l)) # => ?\n",
"\n",
"l = [4]\n",
"print(\"Before append_one: arr={}\".format(l)) # => ?\n",
"append_one(l)\n",
"print(\"After append_one: arr={}\".format(l)) # => ?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Scope\n",
"*Optional Reading: [Python's Execution Model](https://docs.python.org/3/reference/executionmodel.html), especially Section 4.2.2.*\n",
"\n",
"Predict the output of the next two Python programs, then run them to confirm or refute your hypothesis.\n",
"\n",
"```Python\n",
"# Case 1\n",
"x = 10\n",
"\n",
"def foo():\n",
" print(\"(inside foo) x:\", x)\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)\n",
"```\n",
"\n",
"and\n",
"\n",
"```Python\n",
"# Case 2\n",
"x = 10\n",
"\n",
"def foo():\n",
" x = 8 # Only added this line - everything else is the same\n",
" print(\"(inside foo) x:\", x)\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)\n",
"```\n",
"\n",
"Draw a picture of the variable bindings at each scope (global scope and `foo` function-level scope) in each case. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Case 1\n",
"x = 10\n",
"\n",
"def foo():\n",
" print(\"(inside foo) x:\", x)\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Case 2\n",
"x = 10\n",
"\n",
"def foo():\n",
" x = 8 # Only added this line - everything else is the same\n",
" print(\"(inside foo) x:\", x)\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### UnboundLocalError\n",
"\n",
"If we swap just two lines of code, something unusual happens. What is the error? Why might it be happening?\n",
"\n",
"```Python\n",
"x = 10\n",
"\n",
"def foo():\n",
" print(\"(inside foo) x:\", x) # We swapped this line\n",
" x = 8 # with this one\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"x = 10\n",
"\n",
"def foo():\n",
" print(\"(inside foo) x:\", x) # We swapped this line\n",
" x = 8 # with this one\n",
" y = 5\n",
" print('value:', x * y)\n",
"\n",
"print(\"(outside foo) x:\", x)\n",
"foo()\n",
"print(\"(after foo) x:\", x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, `foo` as defined in\n",
"\n",
"```\n",
"lst = [1,2,3]\n",
"def foo():\n",
" lst.append(4)\n",
"foo()\n",
"```\n",
"\n",
"will compile (that is, the function object will be byte-compiled without problem), but\n",
"\n",
"```\n",
"lst = [1,2,3]\n",
"def foo():\n",
" lst = lst + [4]\n",
"foo()\n",
"```\n",
"\n",
"will raise an `UnboundLocalError`. Why? It doesn't, surprisingly, have to do with the fact that `.append` is in place and `+` is not."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# This works\n",
"lst = [1,2,3]\n",
"def foo():\n",
" lst.append(4)\n",
"foo()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# This doesn't\n",
"lst = [1,2,3]\n",
"def foo():\n",
" lst = lst + [4]\n",
"foo()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is such a common problem that the Python FAQ has [a section](https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value) dedicated to this type of `UnboundLocalError`.\n",
"\n",
"*Note, the `global` and `nonlocal` keywords can be used to assign to a variable outside of the currently active (innermost function) scope. If you're interested, you can read more about scoping rules in the optional reading, or in the [appropriate FAQ section](https://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python).*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Default Mutable Arguments - A Dangerous Game\n",
"\n",
"A function's default values are evaluated at the point of function definition in the defining scope. For example:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = 5\n",
"\n",
"def square(num=x):\n",
" return num * num\n",
"\n",
"x = 6\n",
"print(square()) # => 25, not 36\n",
"print(square(x)) # => 36"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Warning: A function's default values are evaluated *only once*, when the function definition is encountered. This is important when the default value is a mutable object such as a list or dictionary**\n",
"\n",
"Predict what the following code will do, then run it to test your hypothesis:\n",
"\n",
"```Python\n",
"def append_twice(a, lst=[]):\n",
" lst.append(a)\n",
" lst.append(a)\n",
" return lst\n",
" \n",
"# Works well when the keyword is provided\n",
"print(append_twice(1, lst=[4])) # => [4, 1, 1]\n",
"print(append_twice(11, lst=[2, 3, 5, 7])) # => [2, 3, 5, 7, 11, 11]\n",
"\n",
"# But what happens here?\n",
"print(append_twice(1))\n",
"print(append_twice(2))\n",
"print(append_twice(3))\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Something fishy is going on here. Can you deduce what is happening?\n",
"def append_twice(a, lst=[]):\n",
" lst.append(a)\n",
" lst.append(a)\n",
" return lst\n",
" \n",
"# Works well when the keyword is provided\n",
"print(append_twice(1, lst=[4])) # => [4, 1, 1]\n",
"print(append_twice(11, lst=[2, 3, 5, 7])) # => [2, 3, 5, 7, 11, 11]\n",
"\n",
"# But what happens here?\n",
"print(append_twice(1))\n",
"print(append_twice(2))\n",
"print(append_twice(3))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"After you run the code, you should see the following printed to the screen:\n",
"\n",
"```\n",
"[1, 1]\n",
"[1, 1, 2, 2]\n",
"[1, 1, 2, 2, 3, 3]\n",
"```\n",
"Discuss with a partner why this is happening.\n",
"\n",
"If you don’t want the default value to be shared between subsequent calls, you can use a sentinel value as the default value (to signal that no keyword argument was explicitly provided by the caller). If so, your function may look something like:\n",
"\n",
"```Python\n",
"def append_twice(a, lst=None):\n",
" if lst is None:\n",
" lst = []\n",
" lst.append(a)\n",
" lst.append(a)\n",
" return lst\n",
"```\n",
"\n",
"Discuss with a partner whether you think this solution feels better or worse."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def append_twice(a, lst=None):\n",
" if lst is None:\n",
" lst = []\n",
" lst.append(a)\n",
" lst.append(a)\n",
" return lst"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Sometimes, however, this odd keyword value initialization behavior can be desirable. For example, it can be used as a cache that is modifiable and accessible by all invocations of a function:\n",
"\n",
"```Python\n",
"def fib(n, cache={0: 1, 1: 1}):\n",
" if n in cache: # Note: default value captures our base cases\n",
" return cache[n]\n",
" out = fib(n-1) + fib(n-2)\n",
" cache[n] = out\n",
" return out\n",
"```\n",
"\n",
"Try running the following code cells."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def fib(n, cache={0: 1, 1: 1}):\n",
" if n in cache: # Note: starting values in the dictionary captures our base cases.\n",
" return cache[n]\n",
" out = fib(n-1) + fib(n-2)\n",
" cache[n] = out\n",
" return out\n",
"\n",
"print(fib(10)) # => 89\n",
"print(fib.__defaults__[0]) # Access the cached dictionary."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Cool, right? The cache follows the function around, as an attribute on the function object, rather than being the responsibility of the caller! Even so, there are better, more Pythonic ways to capture this particular cache design pattern (see [functools.lru_cache](https://docs.python.org/3/library/functools.html#functools.lru_cache)). Nevertheless, it's a neat trick that might come in useful!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Investigating Function Objects\n",
"\n",
"At the end of Tuesday's class, we mentioned that functions are objects, and that they might have interesting attributes to explore. We'll poke around several of these attributes more in depth here.\n",
"\n",
"Usually, this information isn't particularly useful for practitioners (you'll rarely want to hack around with the internals of functions), but even seeing that you *can* in Python is very cool.\n",
"\n",
"In this section, there is no code to write. Instead, you will be reading and running code and observing the output. Nevertheless, we encourage you to play around with the code cells to experiment and explore on your own."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Default Values (`__defaults__` and `__kwdefaults__`)\n",
"\n",
"As stated earlier, any default values (either normal default arguments or the keyword-only default arguments that follow a variadic positional argument parameter) are bound to the function object at the time of function definition. Consider our `all_together` function from earlier, and run the following code. Why might the `__defaults__` attribute be a tuple, but the `__kwdefaults__` attribute be a dictionary?"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options): pass\n",
"\n",
"all_together.__defaults__ # => (1, )\n",
"all_together.__kwdefaults__ # => {'indent':True, 'spaces':4}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Documentation (`__doc__`)\n",
"\n",
"The first string literal in any function, if it comes before any expression, is bound to the function's `__doc__` attribute. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def my_function():\n",
" \"\"\"Summary line: do nothing, but document it.\n",
" \n",
" Description: No, really, it doesn't do anything.\n",
" \"\"\"\n",
" pass\n",
"\n",
"print(my_function.__doc__)\n",
"# Summary line: Do nothing, but document it.\n",
"#\n",
"# Description: No, really, it doesn't do anything."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As stated in lecture, lots of tools use these documentation strings to great advantage. For example, the builtin `help` function displays information from docstrings, and many API-documentation-generation tools like [Sphynx](http://www.sphinx-doc.org/en/stable/) or [Epydoc](http://epydoc.sourceforge.net/) use information contained in the docstring to form smart references and hyperlinks on documentation websites.\n",
"\n",
"Furthermore, the [doctest](https://docs.python.org/3/library/doctest.html) standard library module, in it's own words, \"searches [the documentation string] for pieces of text that look like interactive Python sessions, and then executes those sessions to verify that they work exactly as shown.\" Cool!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Code Object (`__code__`)\n",
"\n",
"In CPython, the reference implementation of Python used by many people (including us), functions are byte-compiled into executable Python code, or _bytecode_, when defined. This code object, which represents the bytecode and some administrative information, is bound to the `__code__` attribute, and has a ton of interesting properties, best illustrated by example. Code objects are immutable and contain no references to immutable objects.\n",
"\n",
"```Python\n",
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options):\n",
" \"\"\"A useless comment\"\"\"\n",
" print(x + y * z)\n",
" print(sum(nums))\n",
" for k, v in options.items():\n",
" if indent:\n",
" print(\"{}\\t{}\".format(k, v))\n",
" else:\n",
" print(\"{}{}{}\".format(k, \" \" * spaces, v))\n",
" \n",
"code = all_together.__code__\n",
"```\n",
"\n",
"| Attribute | Sample Value | Explanation |\n",
"| --- | --- | --- |\n",
"| `code.co_argcount` | `3` | number of positional arguments (including arguments with default values) |\n",
"| `code.co_cellvars` | `()` | tuple containing the names of local variables that are referenced by nested functions |\n",
"| `code.co_code` | `b't\\x00\\x00...\\x04S\\x00'` | string representing the sequence of bytecode instructions |\n",
"| `code.co_consts` | `('A useless comment', '{}\\t{}', '{}{}{}', ' ', None)` | tuple containing the literals used by the bytecode - our `None` is from the implicit `return None` at the end |\n",
"| `code.co_filename` | `filename` or `<stdin>` or `<ipython-input-#-xxx>` | file in which the function was defined |\n",
"| `code.co_firstlineno` | `1` | line of the file the first line of the function appears |\n",
"| `code.co_flags` | `79` | AND of compiler-specific binary flags whose internal meaning is (mostly) opaque to us |\n",
"| `code.co_freevars` | `()` | tuple containing the names of free variables |\n",
"| `code.co_kwonlyargcount` | `2` | number of keyword-only arguments |\n",
"| `code.co_lnotab` | `b'\\x00\\x02\\x10\\x01\\x0c\\x01\\x12\\x01\\x04\\x01\\x12\\x02'` | string encoding the mapping from bytecode offsets to line numbers |\n",
"| `code.co_name` | `\"all_together\"` | the function name |\n",
"| `code.co_names` | `('print', 'sum', 'items', 'format')` | tuple containing the names used by the bytecode |\n",
"| `code.co_nlocals` | `9` | number of local variables used by the function (including arguments) |\n",
"| `code.co_stacksize` | `7` | required stack size (including local variables) |\n",
"| `code.co_varnames` | `('x', 'y', 'z', 'indent', 'spaces', 'nums', 'options', 'k', 'v')` | tuple containing the names of the local variables (starting with the argument names) |\n",
"\n",
"More info on this, and on all types in Python, can be found at the [data model reference](https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy). For code objects, you have to scroll down to \"Internal Types.\""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options):\n",
" \"\"\"A useless comment\"\"\"\n",
" print(x + y * z)\n",
" print(sum(nums))\n",
" for k, v in options.items():\n",
" if indent:\n",
" print(\"{}\\t{}\".format(k, v))\n",
" else:\n",
" print(\"{}{}{}\".format(k, \" \" * spaces, v))\n",
" \n",
"code = all_together.__code__\n",
"\n",
"print(code.co_argcount)\n",
"print(code.co_cellvars)\n",
"print(code.co_code)\n",
"print(code.co_consts)\n",
"print(code.co_filename)\n",
"print(code.co_firstlineno)\n",
"print(code.co_flags)\n",
"print(code.co_freevars)\n",
"print(code.co_kwonlyargcount)\n",
"print(code.co_lnotab)\n",
"print(code.co_name)\n",
"print(code.co_names)\n",
"print(code.co_nlocals)\n",
"print(code.co_stacksize)\n",
"print(code.co_varnames)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Security\n",
"\n",
"As we briefly mentioned in class, this can lead to a pretty glaring security vulnerability. Namely, the code object on a given function can be hot-swapped for the code object of another (perhaps malicious function) at runtime!"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def nice(): print(\"You're awesome!\")\n",
"def mean(): print(\"You're... not awesome. OOOOH\")\n",
"\n",
"# Overwrite the code object for nice\n",
"nice.__code__ = mean.__code__\n",
"\n",
"print(nice()) # prints \"You're... not awesome. OOOOH\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### `dis` module\n",
"\n",
"The `dis` module, for \"disassemble,\" exports a `dis` function that allows us to disassemble Python byte code (at least, for Python distributions implemented in CPython for existing versions). The disassembled code isn't exactly normal assembly code, but rather is a specialized Python syntax\n",
"\n",
"```Python\n",
"def gcd(a, b):\n",
" while b:\n",
" a, b = b, a % b\n",
" return a\n",
" \n",
"import dis\n",
"dis.dis(gcd)\n",
"\"\"\"\n",
" 2 0 SETUP_LOOP 27 (to 30)\n",
" >> 3 LOAD_FAST 1 (b)\n",
" 6 POP_JUMP_IF_FALSE 29\n",
"\n",
" 3 9 LOAD_FAST 1 (b)\n",
" 12 LOAD_FAST 0 (a)\n",
" 15 LOAD_FAST 1 (b)\n",
" 18 BINARY_MODULO\n",
" 19 ROT_TWO\n",
" 20 STORE_FAST 0 (a)\n",
" 23 STORE_FAST 1 (b)\n",
" 26 JUMP_ABSOLUTE 3\n",
" >> 29 POP_BLOCK\n",
"\n",
" 4 >> 30 LOAD_FAST 0 (a)\n",
" 33 RETURN_VALUE\n",
"\"\"\"\n",
"```\n",
"\n",
"Details on the instructions themselves can be found [here](https://docs.python.org/3/library/dis.html#python-bytecode-instructions).\n",
"You can read more about the `dis` module [here](https://docs.python.org/3/library/dis.html)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def gcd(a, b):\n",
" while b:\n",
" a, b = b, a % b\n",
" return a\n",
" \n",
"import dis\n",
"dis.dis(gcd)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Parameter Annotations (`__annotations__`)\n",
"\n",
"Python allows us to add type annotations on functions arguments and return values. This leads to a world of complex possibilities and is still fairly controversial in the Python ecosystem. Nevertheless, it can be used to communicate to your clients expectations for the types of arguments.\n",
"\n",
"Importantly, Python doesn't actually do anything with these annotations and will not check that supplied arguments conform to the type hint specified. This language feature is only made available through the collection of function annotations."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def annotated(a: int, b: str) -> list:\n",
" return [a, b]\n",
"\n",
"print(annotated.__annotations__)\n",
"# => {'b': <class 'str'>, 'a': <class 'int'>, 'return': <class 'list'>}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This information can be used to build some really neat runtime type-checkers for Python!\n",
"\n",
"For more info, check out [PEP 3107](https://www.python.org/dev/peps/pep-3107/) on function annotations or [PEP 484](https://www.python.org/dev/peps/pep-0484/) on type hinting (which was introduced in Python 3.5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Call (`__call__`)\n",
"\n",
"All Python functions have a `__call__` attribute, which is the actual object called when you use parentheses to \"call\" a function. That is,"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def greet(): print(\"Hello world!\")\n",
"\n",
"greet() # \"Hello world!\"\n",
"# is just syntactic sugar for\n",
"greet.__call__() # \"Hello world!\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This means that any object (including instances of custom classes) with a `__call__` method can use the parenthesized function call syntax! For example, we can construct a callable `Polynomial` class. We haven't talked about class syntax yet, so feel free to skip this example.\n",
"\n",
"```Python\n",
"class Polynomial:\n",
" def __init__(self, coeffs):\n",
" \"\"\"Store the coefficients...\"\"\"\n",
" \n",
" def __call__(self, x):\n",
" \"\"\"Compute f(x)...\"\"\"\n",
"\n",
"\n",
"# The polynomial f(x) = 4 + 4 * x + 4 * x ** 2\n",
"f = Polynomial(4, 4, 1)\n",
"f(5) # Really, this is f.__call__(5)\n",
"```\n",
"\n",
"We'll see a lot more about using these so-called \"magic methods\" to exploit Python's apparent operators (like function calling, `+` (`__add__`) or `*` (`__mul__`), etc) in Week 5."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Name Information (`__module__`, `__name__`, and `__qualname__`)\n",
"\n",
"Python functions also store some name information about a function, generally for the purposes of friendly printing.\n",
"\n",
"`__module__` refers to the module that was active at the time the function was defined. Any functions defined in the interactive interpreter, or run as as a script, will have `__module__ == '__main__'`, but imported modules will have their `__module__` attribute set to the module name. For example, `math.sqrt.__module__` is `\"math\"`.\n",
"\n",
"`__name__` is the function's name. Nothing special here.\n",
"\n",
"`__qualname__`, which stands for \"qualified name,\" only differs from `__name__` when you're dealing with nested functions, which we'll talk about more Week 4.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Closure (`__closure__`)\n",
"\n",
"If you're familiar with closures in other languages, Python closures work almost the exact same way. Closures really only arise when dealing with nested functions, so we'll see more Week 4. This bit of text is just to give you a teaser for what's coming soon - yes, Python has closures!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### `inspect` module\n",
"\n",
"As a brief note, all of this mucking around with the internals of Python functions can't be good for our health. Luckily, there's a standard library module for this! The `inspect` module gives us a lot of nice tools for interacting not only with the internals of functions, but also the internals of a lot of other types as well. Check out [the documentation](https://docs.python.org/3/library/inspect.html) for some nice examples."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import inspect\n",
"\n",
"def all_together(x, y, z=1, *nums, indent=True, spaces=4, **options): pass\n",
"\n",
"print(inspect.getfullargspec(all_together))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Finished Early?\n",
"Scan through [PEP 8](https://www.python.org/dev/peps/pep-0008/), Python's official style guide, as well as [PEP 257](https://www.python.org/dev/peps/pep-0257/), Python's suggestions for docstring conventions, if you didn't get a chance to read them last week."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Submitting Labs\n",
"\n",
"Woohoo! There's nothing to officially submit for this lab, but before you go, call over a TA to sign off on your work. After that, you're free to leave as soon as you would like! However, you're also welcome to stick around and work on Assignment 1. :)\n",
"\n",
"**Major credit to PSF for incredibly clear/readable documentation making this all possible, as well as the linked resources.**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> With <3 by @sredmond"
]
}
],
"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.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment