{ "cells": [ { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. meta::\n", " :description: Topic: Functions and Function Signatures, Difficulty: Medium, Category: Section\n", " :keywords: functions, *args, **kwargs, signature, default parameter, docstring, return, vowel count, syntax, basics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Basics of Functions\n", "
\n", "\n", "**Note**: \n", "\n", "There are reading-comprehension exercises included throughout the text. These are meant to help you put your reading to practice. Solutions for the exercises are included at the bottom of this page.\n", "
\n", "\n", "Defining a function allows you to encapsulate a segment of code, specifying the information that enters and leaves the code. You can make use of this \"code-capsule\" repeatedly and in many different contexts. For example, suppose you want to count how many vowels are in a string. The following defines a function that accomplishes this:\n", "\n", "```python\n", "def count_vowels(in_string):\n", " \"\"\" Returns the number of vowels contained in `in_string`\"\"\"\n", " num_vowels = 0\n", " vowels = \"aeiouAEIOU\"\n", " \n", " for char in in_string:\n", " if char in vowels:\n", " num_vowels += 1 # equivalent to num_vowels = num_vowels + 1\n", " return num_vowels\n", "```\n", "\n", "Executing this code will define the *function* `count_vowels`. This function expects to be passed one object, represented by `in_string`, as an *input argument*, and it will *return* the number of vowels stored in that object. Invoking `count_vowels`, passing it an input object, is referred to as *calling* the function:\n", "\n", "```python\n", ">>> count_vowels(\"Hi my name is Ryan\")\n", "5\n", "```\n", "\n", "The great thing about this is that it can be used over and over!\n", "\n", "```python\n", ">>> count_vowels(\"Apple\")\n", "2\n", "\n", ">>> count_vowels(\"envelope\")\n", "4\n", "```\n", "\n", "In this section, we will learn about the syntax for defining and calling functions in Python" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Definition**: \n", "\n", "A Python **function** is an object that encapsulates code. *Calling* the function will execute the encapsulated code and *return* an object. A function can be defined so that it accepts *arguments*, which are objects that are to be passed to the encapsulated code. \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `def` Statement\n", "Similar to `if`, `else`, and `for`, the `def` statement is reserved by the Python language to signify the definition of functions (and a few other things that we'll cover later). The following is the general syntax for defining a Python function:\n", "\n", "```\n", "def ():\n", " \"\"\" documentation string \"\"\"\n", " \n", " return \n", "```\n", "\n", "- `` can be any valid variable name, and *must* be followed by parentheses and then a colon.\n", "- `` specifies the input arguments to the function, and may be left blank if the function does not accept any arguments (the parentheses must still be included, but will not encapsulate anything).\n", "- The documentation string (commonly referred to as a \"docstring\") may span multiple lines, and should indicate what the function's purpose is. It is optional.\n", "- `` can consist of general Python code, and is demarcated by being indented relative to the `def` statement.\n", "- `return` if reached by the encapsulated code, triggers the function to return the specified object and end its own execution immediately.\n", " \n", "The `return` statement is also reserved by Python. It denotes the end of a function; if reached, a `return` statement immediately concludes the execution of the function and returns the specified object. \n", "\n", "Note that, like an if-statement and a for-loop, the `def` statment must end in a colon and the body of the function is [delimited by whitespace](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/Introduction.html#Python-Uses-Whitespace-to-Delimit-Scope):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# wrong indentation\n", "def bad_func1():\n", "x = 1\n", " return x + 2\n", "```\n", "***\n", "```python\n", "# wrong indentation\n", "def bad_func2():\n", " x = 1\n", "return x + 2\n", "```\n", "***\n", "```python\n", "# missing colon\n", "def bad_func3()\n", " x = 1\n", " return x + 2\n", "```\n", "***\n", "```python\n", "# missing parenthesis\n", "def bad_func4:\n", " x = 1\n", " return x + 2\n", "```\n", "\n", "***\n", "```python\n", "# this is ok\n", "def ok_func():\n", " x = 1\n", " return x + 2\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Reading Comprehension: Writing a Basic Function**\n", "\n", "Write a function named `count_even`. It should accept one input argument, named `numbers`, which will be an iterable containing integers. Have the function return the number of even-valued integers contained in the list. Include a reasonable docstring.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The `return` Statement\n", "In general, any Python object can follow a function's `return` statement. Furthermore, an **empty** `return` statement can be specified, or the **return** statement of a function can be omitted altogether. In both of these cases, *the function will return the* `None` *object*.\n", "\n", "```python\n", "# this function returns `None`\n", "# an \"empty\" return statement\n", "def f():\n", " x = 1\n", " return\n", "```\n", "\n", "```python\n", "# this function returns `None`\n", "# return statement is omitted\n", "def f():\n", " x = 1\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All Python functions return *something*. Even the built-in `print` function returns `None` after it prints to standard-output! \n", "\n", "```python\n", "# the `print` function returns `None`\n", ">>> x = print(\"hi\")\n", "hi\n", "\n", ">>> x is None\n", "True\n", "```\n", "
\n", "\n", "**Warning!** \n", "\n", "Take care to not *mistakenly* omit a return statement or leave it blank. You will still be able to call your function, but it will return `None` no matter what!\n", "
\n", "\n", "A function also need not have any additional code beyond its return statement. For example, we can make use of `sum` and a generator comprehension (see the previous section of this module) to shorten our `count_vowels` function:\n", "\n", "```python\n", "# the returned object of a function can be specified straight-away\n", "def count_vowels(in_string): \n", " \"\"\" Returns the number of vowels contained in `in_string`\"\"\"\n", " return sum(1 for char in in_string if char in \"aeiouAEIOU\")\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Multiple `return` Statements\n", "You can specify more than one `return` statement within a function. This can be useful for handling edge-cases or optimizations in your code. Suppose you want your function to compute $e^{x}$, using a [Taylor series](https://en.wikipedia.org/wiki/Taylor_series#Exponential_function) approximation. The function should immediately return `1.0` in the case that $x = 0$:\n", "\n", "```python\n", "def compute_exp(x):\n", " \"\"\" Use a Taylor Series to compute e^x \"\"\"\n", " if x == 0:\n", " return 1.0\n", "\n", " from math import factorial\n", " return sum(x**n / factorial(n) for n in range(100))\n", "```\n", "\n", "If `x==0` is `True`, then the first `return` statement is reached. `1.0` will be returned and the function will be \"exited\" immediately, without ever reaching the code following it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As stated above, a `return` statement will trigger a function to end its execution immediately when reached, even when subsequent code follows it. *It is impossible for multiple `return` statements to be visited within a single function call*. Thus if you want to return multiple items, then your function must return a single container of those items, like a list or a tuple." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "# Returning multiple items from a function\n", "def bad_f(x):\n", " \"\"\" return x**2 and x**3\"\"\"\n", " return x**2 \n", " # this code can never be reached!\n", " return x**3\n", "\n", "def good_f(x):\n", " \"\"\" return x**2 and x**3\"\"\"\n", " return (x**2, x**3)\n", "```\n", "```python\n", ">>> bad_f(2)\n", "4\n", "\n", ">>> good_f(2)\n", "(4, 8)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inline Functions\n", "Functions can be defined in-line, as a single return statement:\n", "\n", "```python\n", "def add_2(x):\n", " return x + 2\n", "```\n", "\n", "can be rewritten as:\n", "\n", "```python\n", "def add_2(x): return x + 2\n", "```\n", "\n", "This should be used sparingly, for exceedingly simple functions that can be easily understood without docstrings." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Arguments\n", "A sequence of comma-separated variable names can be specified in the function signature to indicated *positional* arguments for the function. For example, the following specifies `x`, `lower`, and `upper` as input arguments to a function, `is_bounded`:\n", "\n", "```python\n", "def is_bounded(x, lower, upper):\n", " return lower <= x <= upper\n", "```\n", "\n", "This function can then be passed its arguments in several way:\n", "\n", "### Specifying Arguments by Position\n", "The objects passed to `is_bounded` will be assigned to its input variables based on their positions. That is, `is_bounded(3, 2, 4)` will assign `x=3`, `lower=2`, and `upper=4`, in accordance with the positional ordering of the function's input arguments:\n", "\n", "```python\n", "# evaluate: 2 <= 3 <= 4\n", "# specifying inputs based on position\n", ">>> is_bounded(3, 2, 4)\n", "True\n", "```\n", "\n", "Feeding a function too few or too many arguments will raise a `TypeError`\n", "```python\n", "# too few inputs: raises error\n", "is_bounded(3)\n", "\n", "# too many inputs: raises error\n", "is_bounded(1, 2, 3, 4)\n", "```\n", "\n", "### Specifying Arguments by Name\n", "You can provide explicit names when specifying the inputs to a function, in which case ordering does not matter. This is very nice for writing clear and flexible code:\n", "```python\n", "# evaluate: 2 <= 3 <= 4\n", "# specify inputs using explicit input names\n", ">>> is_bounded(lower=2, x=3, upper=4)\n", "True\n", "```\n", "\n", "You can mix-and-match positional and named input by using position-based inputs first:\n", "\n", "```python\n", "# evaluate: 2 <= 3 <= 4\n", "# `x` is specified based on position\n", "# `lower` and `upper` are specified by name\n", ">>> is_bounded(3, upper=4, lower=2)\n", "True\n", "```\n", "\n", "Note that if you provide a named input, all the inputs following it must also be named:\n", "\n", "```python\n", "# positional arguments cannot follow named arguments\n", ">>> is_bounded(3, lower=2, 4)\n", "SyntaxError: positional argument follows keyword argument\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Default-Valued Arguments\n", "You can specify default values for input arguments to a function. Their default values are utilized if a user does not specify these inputs when calling the function. Recall our `count_vowels` function. Suppose we want the ability to include \"y\" as a vowel. We know, however, that people will typically want to exclude \"y\" from their vowels, so we can exclude \"y\" by default:\n", "\n", "```python\n", "def count_vowels(in_string, include_y=False): \n", " \"\"\" Returns the number of vowels contained in `in_string`\"\"\"\n", " vowels = \"aeiouAEIOU\"\n", " if include_y:\n", " vowels += \"yY\" # add \"y\" to vowels \n", " return sum(1 for char in in_string if char in vowels)\n", "```\n", "\n", "Now, if only `in_string` is specified when calling `count_vowels`, `include_y` will be passed the value `False` by default:\n", "\n", "```python\n", "# using the default value: exclude y from vowels\n", ">>> count_vowels(\"Happy\")\n", "1\n", "```\n", "\n", "This default value can be overridden:\n", "```python\n", "# overriding the default value: include y as a vowel\n", ">>> count_vowels(\"Happy\", True)\n", "2\n", "\n", "# you can still specify inputs by name\n", ">>> count_vowels(include_y=True, in_string=\"Happy\")\n", "2\n", "```\n", "\n", "Default-valued input arguments must come after all positional input arguments in the function signature:\n", "```python\n", "# this is ok\n", "def f(x, y, z, count=1, upper=2):\n", " return None\n", "```\n", "\n", "```python\n", "# this will raise a syntax error\n", "def f(x, y, count=1, upper=2, z):\n", " return None\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Reading Comprehension: Functions and Arguments**\n", "\n", "Write a function, `max_or_min`, which accepts two positional arguments, `x` and `y` (which will hold numerical values), and a `mode` variable that has the default value `\"max\"`. \n", "\n", "The function should return `min(x, y)` or `max(x, y)` according to the `mode`. Have the function return `None` if `mode` is neither `\"max\"` nor `\"min\"`. \n", "\n", "Include a descriptive doc-string.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accommodating an Arbitrary Number of Positional Arguments\n", "Python provides us with a syntax for defining a function, which can be called with an arbitrary number of positional arguments. This is signaled by the syntax `def f(*)`.\n", "\n", "```python\n", "# The * symbol indicates that an arbitrary number of\n", "# arguments can be passed to `args`, when calling `f`.\n", "def f(*args):\n", " # All arguments passed to `f` will be \"packed\" into a \n", " # tuple that is assigned to the variable `args`.\n", " # `f()` will assign `args = tuple()`\n", " # `f(x, y, ...)` will assign `args = (x, y, ...)`\n", " return args\n", "```\n", "\n", "Because Python cannot foresee how many arguments will be passed to `f`, all of the objects that are passed to it will be *packed into a tuple*, which is then assigned to the variable `args`:\n", "\n", "```python\n", "# pass zero arguments to `f`\n", ">>> f() \n", "()\n", "\n", "# pass one argument to `f`\n", ">>> f(1) \n", "(1,)\n", "\n", "# pass three arguments to `f`\n", ">>> f((0, 1), True, \"cow\") \n", "((0, 1), True, \"cow\")\n", "```\n", "\n", "This syntax can be combined with positional arguments and default arguments. Any variables specified after a packed variable *must be called by name*:\n", "```python\n", "def f(x, *seq, y):\n", " print(\"x is: \", x)\n", " print(\"seq is: \", seq)\n", " print(\"y is: \", y)\n", " return None\n", "```\n", "```python\n", ">>> f(1, 2, 3, 4, y=5) # `y` must be specified by name\n", "```\n", "```\n", "x is: 1\n", "seq is: (2, 3, 4)\n", "y is: 5\n", "```\n", "```python\n", ">>> f(\"cat\", y=\"dog\") # no additional positional arguments are passed\n", "```\n", "```\n", "x is: \"cat\"\n", "seq is: ()\n", "y is: \"dog\"\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "**Reading Comprehension: Arbitrary Arguments**\n", "\n", "Write a function named `mean`, which accepts and arbitrary number of numerical arguments, and computes the mean of all of the values passed to the function. Thus `mean(1, 2, 3)` should return $\\frac{1 + 2 + 3}{3} = 2.0$ \n", "\n", "This function should return `0.` if no arguments are passed to it. Be sure to test your function, and include a docstring.\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that `*` indicates the *packing of an arbitrary number of arguments into a tuple*, when used in the signature of a function definition. Simultaneously, `*` signals the *unpacking of an iterable* to pass each of its members as a positional argument to a function, when used in the context of calling a function:\n", "\n", "```python\n", "# Using `*` when calling a function, to unpack an \n", "# iterable. Passing its members as distinct arguments \n", "# to the function\n", "\n", "def f(x, y, z):\n", " return x + y + z\n", "\n", ">>> f(1, 2, 3)\n", "6\n", "\n", "# `*` means: unpack the contents of [1, 2, 3]\n", "# passing each item as x, y, and z,\n", "# respectively\n", ">>> f(*[1, 2, 3]) # equivalent to: f(1, 2, 3)\n", "6\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, we use `*` to: \n", "\n", " 1. Define a function to accept an arbitrary\n", " number of arguments, which get packed into a tuple.\n", " 2. Call the function, passing it an arbitrary \n", " number of arguments, by unpacking an iterable." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def number_of_args(*args):\n", " return len(args)\n", "```\n", "```python\n", ">>> number_of_args(None, None, None, None)\n", "4\n", "\n", ">>> some_list = [1, 2, 3, 4, 5]\n", "\n", "# passing the list itself as the sole argument\n", ">>> number_of_args(some_list)\n", "1\n", "\n", "# unpacking the 5 members of the list, \n", "# passing each one as an argument to the function\n", ">>> number_of_args(*some_list)\n", "5\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accommodating an Arbitrary Number of Keyword Arguments\n", "We can also define a function that is able to accept an arbitrary number of *keyword* arguments, using the syntax: `def f(**)` \n", "\n", "Note that a single asterisk, `*`, was used to denote an arbitrary number of *positional* arguments, whereas `**` signals the acceptance of an arbitrary number of *keyword* arguments. \n", "\n", "```python\n", "# The ** symbol indicates that an arbitrary number of\n", "# keyword arguments can be passed to `args`, when calling `f`.\n", "def f(**args):\n", " # All keyword arguments passed to `f` will be \"packed\" into a \n", " # dictionary that is assigned to the variable `args`.\n", " # `f()` will assign `args = {}` (an empty dictionary)\n", " # `f(x=1, y=2, ...)` will assign `args = {\"x\":1, \"y\":2, ...}`\n", " return args\n", "```\n", "\n", "Because Python cannot foresee how many arguments will be passed to `f`, all of the keyword arguments that are passed to it will be packed into a *dictionary*, where a given keyword is set as a key (cast as a string) that maps to the corresponding value. This dictionary is then assigned to the variable `args`. Dictionaries will be discussed in detail in a [later section](https://www.pythonlikeyoumeanit.com/Module2_EssentialsOfPython/DataStructures_II_Dictionaries.html).\n", "\n", "```python\n", ">>> f() # pass zero arguments to `f`\n", "{}\n", "\n", ">>> f(x=1) # pass one argument to `f`\n", "{'x': 1}\n", "\n", ">>> f(x=(0, 1), val=True, moo=\"cow\") # pass three arguments to `f`\n", "{'moo': 'cow', 'val': True, 'x': (0, 1)}\n", "```\n", "\n", "This syntax can be combined with positional arguments and default arguments. No additional arguments may come after a `**` entry in a function-definition signature:\n", "```python\n", "def f(x, y=2, **kwargs):\n", " print(\"x is: \", x)\n", " print(\"y is: \", y)\n", " print(\"kwargs is: \", kwargs)\n", " return None\n", "```\n", "```python\n", "# passing arbitrary keyword arguments to `f`\n", ">>> f(1, y=9, z=3, k=\"hi\")\n", "```\n", "```\n", "x is: 1\n", "y is: 9\n", "kwargs is: {'z': 3, 'k': 'hi'}\n", "```\n", "```python\n", "# no additional keyword arguments are passed\n", ">>> f(\"cat\", y=\"dog\") \n", "```\n", "```\n", "x is: cat\n", "y is: dog\n", "kwargs is: {}\n", "```\n", "\n", "The following function accepts an arbitrary number of positional arguments *and* an arbitrary number of keyword arguments:\n", "\n", "```python\n", "# accepting arbitrary positional and keyword arguments\n", "def f(*x, **y):\n", " # all positional arguments get packed into the tuple `x`\n", " # all keyword arguments get packed into the dictionary `y` \n", " print(x) \n", " print(y)\n", " return None\n", "\n", ">>> f(1, 2, 3, hi=-1, bye=-2, sigh=-3)\n", "```\n", "```\n", "(1, 2, 3)\n", "{'hi': -1, 'bye': -2, 'sigh': -3}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that `**` indicates the *packing of an arbitrary number of keyword arguments into a dictionary*, when used in the signature of a function definition. Simultaneously, `**` signals the *unpacking of a dictionary* to pass each of its key-value pairs as a keyword argument to a function, when used in the context of calling a function:\n", "\n", "```python\n", "# Using `**` when calling a function, to unpack a \n", "# dictionary, passing its members as keyword arguments \n", "# to the function\n", "def f(x, y, z):\n", " return 0*x + 1*y + 2*z\n", "\n", ">>> f(z=10, x=9, y=1)\n", "21\n", "\n", ">>> args = {\"x\": 9, \"y\": 1, \"z\": 10}\n", ">>> f(**args) # equivalent to: f(x=9, y=1, z=10)\n", "21\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, we use `**` to: \n", "\n", " 1. Define a function to accept an arbitrary\n", " number of keyword arguments, which get packed into a dictionary.\n", " 2. Call the function, passing it an arbitrary \n", " number of keyword arguments, by unpacking a dictionary." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```python\n", "def print_kwargs(**args):\n", " print(args)\n", "```\n", "```python\n", ">>> print_kwargs(a=1, b=2, c=3, d=4)\n", "{'a': 1, 'b': 2, 'c': 3, 'd': 4}\n", "\n", ">>> some_dict = {\"hi\":1, \"bye\":2}\n", "\n", "# unpacking the key-value pairs of the dictionary\n", "# as keyword arguments and values, to the function\n", ">>> print_kwargs(a=2, umbrella=True, **some_dict)\n", "{'a': 2, 'umbrella': True, 'hi': 1, 'bye': 2}\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions are Objects\n", "Once defined, a function behaves like any other Python object, like a list or string or integer. You can assign a variable to a function-object:\n", "```python\n", ">>> var = count_vowels # `var` now references the function `count_vowels`\n", ">>> var(\"Hello\") # you can now \"call\" `var`\n", "2\n", "```\n", "\n", "You can store functions in a list:\n", "```python\n", "my_list = [count_vowels, print]\n", "\n", "for func in my_list:\n", " func(\"hello\")\n", " \n", "# iteration 0: calls `count_vowels(\"hello\")` \n", "# iteration 1: calls `print(\"hello\")`\n", "```\n", "\n", "You can also call functions anywhere, and their return-value will be returned in-place:\n", "```python\n", "if count_vowels(\"pillow\") > 1:\n", " print(\"that's a lot of vowels!\")\n", "```\n", "\n", "And, of course, this works within comprehension expressions as well:\n", "```python\n", ">>> sum(count_vowels(word, include_y=True) for word in [\"hi\", \"bye\", \"guy\", \"sigh\"])\n", "6\n", "```\n", "\n", "\"Printing\" a function isn't very revealing. It simply tells you the memory address where the function-object is stored:\n", "```python\n", ">>> print(count_vowels)\n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Links to Official Documentation\n", "\n", "- [Definition of 'function'](https://docs.python.org/3/library/stdtypes.html#functions)\n", "- [Defining functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)\n", "- [Default argument values](https://docs.python.org/3/tutorial/controlflow.html#default-argument-values)\n", "- [Keyword arguments](https://docs.python.org/3/tutorial/controlflow.html#keyword-arguments)\n", "- [Specifying arbitrary arguments](https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists)\n", "- [Unpacking arguments](https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists)\n", "- [Documentation strings](https://docs.python.org/3/tutorial/controlflow.html#documentation-strings)\n", "- [Function annotations](https://docs.python.org/3/tutorial/controlflow.html#function-annotations)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reading Comprehension Exercise Solutions:\n", "\n", "**Writing a Basic Function: Solution**\n", "\n", "```python\n", "def count_even(numbers):\n", " \"\"\" Counts the number of even integers in an iterable\"\"\"\n", " total = 0\n", " for num in numbers:\n", " if num % 2 == 0:\n", " total += 1\n", " return total\n", "```\n", "or, using a generator comprehension:\n", "\n", "```python\n", "def count_even(numbers):\n", " \"\"\" Counts the number of even integers in an iterable\"\"\"\n", " return sum(1 for num in numbers if num % 2 == 0)\n", "```\n", "\n", "**Functions and Arguments: Solution**\n", "\n", "```python\n", "def max_or_min(x, y, mode=\"max\"):\n", " \"\"\" Return either `max(x,y)` or `min(x,y)`,\n", " according to the `mode` argument.\n", " \n", " Parameters\n", " ----------\n", " x : Number\n", " \n", " y : Number\n", " \n", " mode : str\n", " Either 'max' or 'min'\n", " \n", " Returns\n", " -------\n", " The max or min of the two values. `None` is\n", " returned if an invalid mode was specified.\"\"\"\n", " if mode == \"max\":\n", " return max(x, y)\n", " elif mode == \"min\":\n", " return min(x, y)\n", " else:\n", " return None\n", "```\n", "\n", "Note that you can actually have your function raise an \"exception\" (an error) in the case that `mode` wasn't passed a proper value. In fact, that is likely the more appropriate behavior for this function. \n", "\n", "Such a solution would look like:\n", "```python\n", "def max_or_min(x, y, mode=\"max\"):\n", " if mode == \"max\":\n", " return max(x, y)\n", " elif mode == \"min\":\n", " return min(x, y)\n", " else:\n", " raise Exception(\"`mode` was passed an invalid value: {}\".format(mode))\n", "```\n", "\n", "**Arbitrary Arguments: Solution**\n", "\n", "```python\n", "def mean(*seq):\n", " \"\"\" Returns the mean of the function's arguments \"\"\"\n", " if len(seq) == 0:\n", " return 0\n", " \n", " total = 0 \n", " for num in seq:\n", " total += num\n", " return total / len(seq)\n", "```\n", "\n", "or, being a bit more fancy :\n", "\n", "- using the fact that `bool(seq)` is `False` if `seq` is empty\n", "- using the inline if-else syntax\n", "\n", "```python\n", "def mean(*seq):\n", " \"\"\" Returns the mean of the function's arguments \"\"\"\n", " return sum(seq) / len(seq) if seq else 0\n", "```" ] } ], "metadata": { "jupytext": { "text_representation": { "extension": ".md", "format_name": "markdown", "format_version": "1.3", "jupytext_version": "1.13.6" } }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 4 }