|
6 | 6 | %
|
7 | 7 | \hsection{Defining and Calling Functions}%
|
8 | 8 | %
|
9 |
| -\pythonIdx{def}% |
| 9 | +\pythonIdx{def}\pythonIdx{function!def}% |
10 | 10 | The syntax for defining our own functions in \python\ is as follows:%
|
11 | 11 | %
|
12 | 12 | \begin{pythonSyntax}
|
|
33 | 33 | my_function(argument_1, argument_2) # we can call the function like this
|
34 | 34 | \end{pythonSyntax}
|
35 | 35 | %
|
36 |
| -\pythonIdx{function!def}A function in \python\ is created by using the \pythonilIdx{def} keyword, followed by the name of the function.% |
| 36 | +\pythonIdx{function!def}A function in \python\ is created by using the \pythonilIdx{def}\pythonIdx{function!def} keyword, followed by the name of the function.% |
37 | 37 | %
|
38 | 38 | \bestPractice{functionNames}{Function names should be lower case, with underscores separating multiple words if need be~\cite{PEP8}.}%
|
39 | 39 | %
|
40 |
| -\pythonil{function!parameters}Then follows an opening and a closing parenthesis, i.e., \pythonil{(...)}\pythonIdx{(\idxdots)}. |
| 40 | +\pythonil{function!parameters}\pythonIdx{function!parameter}Then follows an opening and a closing parenthesis, i.e., \pythonil{(...)}\pythonIdx{(\idxdots)}. |
41 | 41 | A function can have parameters through which we can pass values to it.
|
42 | 42 | Inside the function, these parameters act like variables.
|
43 |
| -The values of these variables can be passed in when we call (invoke, execute) the function. |
| 43 | +The values of these variables can be passed in when we call (invoke, execute) the function.% |
| 44 | +% |
| 45 | +\begin{definition}[Parameter]\pythonIdx{function!parameter}% |
| 46 | +A function \emph{parameter} is a variable defined inside the function that receives its value when the function is called.% |
| 47 | +\end{definition}% |
| 48 | +% |
44 | 49 | Notice that, just like variables, all such parameters should be annotated with type hints~(see \cref{sec:variableTypesAndTypeHints}).
|
45 | 50 | Functions can return results~(like the \pythonilIdx{sqrt} function of the \pythonilIdx{math} module does) or return nothing~(like \pythonilIdx{print}).
|
46 | 51 | \pythonIdx{function!return value}If they return a result, the type of this result is specified via the type hint \pythonil{ -> result_type}\pythonIdx{->}.
|
47 | 52 | The function header ends with a colon~(\pythonilIdx{:}).%
|
48 | 53 | %
|
49 |
| -\bestPractice{functionTypeHints}{% |
| 54 | +\bestPractice{functionTypeHints}{\pythonIdx{function!type hint}% |
50 | 55 | All parameters and the return value of a function should be annotated with type hints. %
|
51 | 56 | From my perspective: \emph{A function without type hints is wrong.}%
|
52 | 57 | }%
|
|
66 | 71 | This follows the same pattern of function calls that we already used in many of our examples.%
|
67 | 72 | \end{sloppypar}%
|
68 | 73 | %
|
| 74 | +\begin{definition}[Argument]\pythonIdx{function!argument}% |
| 75 | +An \emph{argument} is the actual value given for a function parameter when the function is called.% |
| 76 | +\end{definition}% |
| 77 | +% |
69 | 78 | \pythonIdx{function!docstring}\pythonIdx{str!doc!function}%
|
70 | 79 | Between the header of a function and its body, we always need to place a so-called \pgls{docstring}, which is a multi-line string~(see \cref{sec:multiLineStrings}).
|
71 | 80 | This string consists of a title line shortly describing what the function does.
|
|
178 | 187 | Instead, it will print the \pythonil{gcd} nicely using \pythonilIdx{print} and an \pgls{fstring}.
|
179 | 188 | Since the \pythonilIdx{math} module also provides a function names \pythonilIdx{gcd} for computing, well, the greatest common divisor, we want to compare the result of our function with this one.%
|
180 | 189 | %
|
181 |
| -\begin{sloppypar}% |
| 190 | +\begin{sloppypar}\pythonIdx{function!import}% |
182 | 191 | Of course, we cannot have two functions named \pythonil{gcd} in the same context.
|
183 | 192 | So we import the function from the \pythonilIdx{math} module \emph{under a different name}:
|
184 | 193 | \pythonil{from math import gcd as math_gcd}\pythonIdx{as}\pythonIdx{import}\pythonIdx{from} makes the \pythonilIdx{gcd} function from the module \pythonilIdx{math} available under the name~\pythonil{math_gcd}.
|
|
193 | 202 | \endhsection%
|
194 | 203 | %
|
195 | 204 | \hsection{Functions in Modules}%
|
| 205 | +\pythonIdx{module!import}\pythonIdx{function!import}\pythonIdx{function!module}% |
196 | 206 | %
|
197 | 207 | \gitPython{\programmingWithPythonCodeRepo}{05_functions/my_math.py}{--args format}{functions:my_math}{%
|
198 | 208 | The module \pythonil{my_math}, which provides two mathematics functions, namely \pythonil{sqrt}, implementing the algorithm of Heron to compute the square root from \cref{lst:loops:while_loop_sqrt}, and \pythonil{factorial}, copied from \cref{lst:functions:def_factorial}.}%
|
|
262 | 272 | %
|
263 | 273 | \hsection{Unit Testing}%
|
264 | 274 | \label{sec:unitTesting}%
|
| 275 | +\pythonIdx{function!unit test}% |
| 276 | +\pythonIdx{function!testing}% |
265 | 277 | %
|
266 | 278 | Structuring our code into functions and modules has several advantages.
|
267 | 279 | We can reuse code and we can divide big application into smaller pieces, both of which make it easier to understand what our program is doing.
|
|
464 | 476 | }%
|
465 | 477 | \endhsection%
|
466 | 478 | %
|
| 479 | +\hsection{Function Arguments: Default Values, Passing them by Name, and Constructing them}% |
| 480 | +% |
| 481 | +\gitPython{\programmingWithPythonCodeRepo}{05_functions/normal_pdf.py}{--args format}{functions:normal_pdf}{% |
| 482 | +Implementing the probability density function~(PDF) of the normal distribution as function with default argument values.\pythonIdx{pi}\pythonIdx{exp}\pythonIdx{sqrt}}% |
| 483 | +% |
| 484 | +\gitPythonAndOutput{\programmingWithPythonCodeRepo}{05_functions}{use_normal_pdf.py}{--args format}{functions:use_normal_pdf}{% |
| 485 | +Using the PDF of the normal distribution implemented in \cref{lst:functions:normal_pdf}.}% |
| 486 | +% |
| 487 | +After the brief introduction into unit testing, let us now come to a lighter topic: passing arguments to functions.% |
| 488 | +We have already seen examples for this. |
| 489 | +Our \pythonil{gcd} function from back in \cref{lst:functions:def_gcd} has two parameters \pythonil{a} and \pythonil{b} and we can invoke it by writing the values of these parameters in parentheses. |
| 490 | +\pythonil{gcd(12, 4)} will invoke \pythonil{gcd} and assign \pythonil{12} to \pythonil{a} and \pythonil{4} to \pythonil{b}. |
| 491 | + |
| 492 | +We can also let parameters have so-called \emph{default values}.\pythonIdx{function!parameter!default value}\pythonIdx{function!argument!default} |
| 493 | +If a parameter has a default value, then we can either specify the value of the parameter when calling the function \emph{or} we can simply omit it, i.e., not assign a value to it. |
| 494 | +In the latter case, the parameter will then have the default value. |
| 495 | +From inside the function, this looks the same as if we passed in the default value. |
| 496 | + |
| 497 | +As a simple example, let us implement the probability density function~(PDF) of the normal distribution. |
| 498 | +You may remember from high school math that this function, let's call it~$f$, defined as% |
| 499 | +% |
| 500 | +\begin{equation}% |
| 501 | +f(x, \mu, \sigma) = \frac{1}{\sqrt{2\numberPi\sigma^2}} e^{\frac{-(x-\mu)^2}{2\sigma^2}}% |
| 502 | +\label{eq:normalDistributionPdf}% |
| 503 | +\end{equation}% |
| 504 | +% |
| 505 | +\begin{figure}% |
| 506 | +\centering% |
| 507 | +\includegraphics[width=0.65\linewidth]{\currentDir/normalDistPdf}% |
| 508 | +\caption{A sketch of the probability density function of the normal distribution given in \cref{eq:normalDistributionPdf}.}% |
| 509 | +\label{fig:normalDistPdf} |
| 510 | +\end{figure}% |
| 511 | +% |
| 512 | +Here, $\mu$~is the arithmetic mean, i.e., the expected value, of the distribution and $\sigma$~is its standard deviation~(making $\sigma^2$~its variance). |
| 513 | +$x$~is the variable of this function. |
| 514 | +This function describes typical bell-shaped curve of the normal distribution as sketched in \cref{fig:normalDistPdf}. |
| 515 | +Implementing this function as a, well, function in \python\ is straightforward. |
| 516 | +\Cref{lst:functions:normal_pdf}\pythonIdx{pi}\pythonIdx{exp}\pythonIdx{sqrt} offers the function \pythonil{pdf} with three parameters: \pythonil{x}, \pythonil{mu}, and~\pythonil{sigma}, which represent~$x$, $\mu$, and~$\sigma$.% |
| 517 | +% |
| 518 | +\begin{sloppypar}\pythonIdx{function!call}%% |
| 519 | +Now, the two parameters~$\mu$ and~$\sigma$ of~$f$ (respectively~\pythonil{mu} and~\pythonil{sigma} of~\pythonil{pdf}) represent the general normal distribution. |
| 520 | +The \emph{standard} normal distribution has $\mu=0$ and $\sigma=1$, i.e., is centered around the mean~$0$ and has a standard deviation (and variance) of~$1$. |
| 521 | +We therefore define the \emph{default} values for \pythonil{mu} to be~\pythonil{0.0} and for \pythonil{sigma} to be~\pythonil{1.0}. |
| 522 | +This is done directly in the header of the function. |
| 523 | +Instead of writing \pythonil{x: float, mu: float, sigma: float}, we write \pythonil{x: float, mu: float = 0.0, sigma: float = 1.0}.% |
| 524 | +\end{sloppypar}% |
| 525 | +% |
| 526 | +In \cref{lst:functions:use_normal_pdf}, we import our new \pythonil{pdf} function and use it inside a program. |
| 527 | +When calling \pythonil{pdf}, we can omit the values of the parameters with default values, in which case they will take on their default values. |
| 528 | +For example, invoking \pythonil{pdf(0.0)} if equivalent to calling~\pythonil{pdf(0.0, 0.0, 1.0)}. |
| 529 | +We also can specify some of the parameters with default values while omitting others. |
| 530 | +For instance, the function call \pythonil{pdf(2.0, 3.0)} is the same as \pythonil{pdf(2.0, 3.0, 1.0)}. |
| 531 | +Obviously, we must always specify the first parameter~(\pythonil{x}), because it has no default value.% |
| 532 | +% |
| 533 | +\bestPractice{defaultValues}{% |
| 534 | +\pythonIdx{function!parameter!default value}\pythonIdx{function!argument!default}% |
| 535 | +Default parameter values must always be immutable.}% |
| 536 | +% |
| 537 | +The default value of a function must always be immutable. |
| 538 | +If you would pass in, e.g., a \pythonil{list}, then the function could modify the list and the next call to this function would then receive this modified list. |
| 539 | +Even worse, if the function was to return the list, it could be modified outside of the function. |
| 540 | +The behavior of such code could become arbitrarily hard to debug. |
| 541 | + |
| 542 | +Back to business.\pythonIdx{function!parameter!by name}\pythonIdx{function!argument!by name} |
| 543 | +What would we do if we want to specify the value of the parameter \pythonil{sigma} of our function, but leave \pythonil{mu} at its default value? |
| 544 | +We can do this by passing in values by parameter name: |
| 545 | +\pythonil{pdf(-2.0, sigma=3.0)}~passes in \pythonil{-2.0} for~\pythonil{x} and \pythonil{3.0} for~\pythonil{sigma}. |
| 546 | +It does not specify any value for~\pythonil{mu}, leaving it at its default value, which renders the call equivalent to~\pythonil{pdf(-2.0, 0.0, 3.0)}. |
| 547 | +This passing in of arguments by specifying \pythonil{parameterName=value} also allows us to specify the arguments in arbitrary order. |
| 548 | +\pythonil{pdf(mu=8.0, x=0.0, sigma=1.5)} is an example of this. |
| 549 | +Don't do such things, though.% |
| 550 | +% |
| 551 | +\begin{sloppypar}% |
| 552 | +\Cref{lst:functions:use_normal_pdf} provides also another interesting way to call a function in \python. |
| 553 | +As we have established by now, the parameters of a function have names. |
| 554 | +If we write something like \pythonil{mu=8.0, x=0.0, sigma=1.5} to assign arguments, this looks very similar to the way we created dictionary constants back in \cref{sec:dictionaries} and \cref{lst:dicts:dicts_1}. |
| 555 | +Calling \pythonil{pdf(-2.0, sigma=3.0)} is equivalent to writing~\pythonil{pdf(x=-2.0, sigma=3.0)}.% |
| 556 | +\end{sloppypar}% |
| 557 | +% |
| 558 | +We can create a dictionary with the values \pythonil{\{"x": -2.0, "sigma": 3.0\}}. |
| 559 | +Let's call this dictionary~\pythonil{args_dict}. |
| 560 | +Can we now somehow pass in these values to~\pythonil{pdf}? |
| 561 | +We indeed can: |
| 562 | +We just have to write~\pythonil{pdf(**args_dict)}.\pythonIdx{**!function parameter}\pythonIdx{function!parameter!**}\pythonIdx{function!argument!**}\pythonIdx{function!argument!keyword}\pythonIdx{dict} |
| 563 | +Doing this will unpack the dictionary \pythonil{args_dict} and pass all the values under their assigned names in as arguments to their corresponding parameters. |
| 564 | +\pythonil{pdf(**args_dict)} is thus equivalent to~\pythonil{pdf(x=-2.0, sigma=3.0)}. |
| 565 | +Two things are to notice here: |
| 566 | +First, the double~\pythonil{*}\pythonIdx{*!function parameter}\pythonIdx{function!argument!*}\pythonIdx{function!parameter!*} (called % |
| 567 | +wildcard\pythonIdx{wildcard}\pythonIdx{function!argument!wildcard}\pythonIdx{function!parameter!wildcard}, % |
| 568 | +star\pythonIdx{star}\pythonIdx{function!argument!star}\pythonIdx{function!parameter!star}, or % |
| 569 | +asterisk\pythonIdx{asterisk}\pythonIdx{function!argument!asterisk}\pythonIdx{function!parameter!asterisk}) % |
| 570 | +\emph{before} the dictionary, i.e., the~\pythonil{**}\pythonIdx{**!function parameter} is telling \python\ to unpack the dictionary this way. |
| 571 | +Second, default argument values still apply here, i.e., \pythonil{mu} will have value~\pythonil{0.0} in this function call. |
| 572 | + |
| 573 | +Similarly, maybe we do not care about the parameter names but want pass them in by position, as we have always done in the past. |
| 574 | +Then, we can construct a sequence, e.g., a \pythonilIdx{list} or \pythonilIdx{tuple} with the parameter values. |
| 575 | +Of course, \pythonilsIdx{list} and \pythonilsIdx{tuple} do not store key-value relationships, only values at positions. |
| 576 | +We could create a tuple~\pythonil{args_tuple} with the value~\pythonil{(-2.0, 7.0, 3.0)}. |
| 577 | +Then, invoking \pythonil{pdf(*args_tuple)} will basically fill in the three values in their into the parameters, i.e., will be equivalent to~\pythonil{pdf(-2.0, 7.0, 3.0)}. |
| 578 | +This time, only a single wildcard~\pythonil{*}\pythonIdx{*!function parameter}\pythonIdx{function!argument!*}\pythonIdx{function!parameter!*} is placed before~\pythonil{args_tuple}. |
| 579 | +We can also pass the parameters in by \inQuotes{unpacking} a list. |
| 580 | +In our example \cref{lst:functions:use_normal_pdf}, we create the list \pythonil{args_list = [2.0, 3.0]}. |
| 581 | +Calling~\pythonil{pdf(*args_list)} then is the same as writing~\pythonil{pdf(2.0, 3.0)}, which, in turn, is identical to~\pythonil{pdf(2.0, 3.0, 1.0)}. |
| 582 | +Again, parameters with default values do not need to be supplied. |
| 583 | + |
| 584 | +At first glance, the use of all of the above is not entirely clear. |
| 585 | +What do we need default parameter values for? |
| 586 | +Well, in some cases, you may want to enable a user to \inQuotes{customize} your functions. |
| 587 | +A typical example is the \href{https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.plot.html}{\pythonilIdx{plot} method} of the \pythonilIdx{Axes} object provided by popular \matplotlib\ library. |
| 588 | +You will normally provide a sequence of x-\ and y\nobreakdash-coordinates to this function it will draw a line which goes through all the points specified this way. |
| 589 | +However, you can also optionally specify a color for the line, markers to be painted at the points, line dash style, a label, colors and sizes for the markers, a z\nobreakdash-order to be used if multiple lines are drawn, and so on, and so on. |
| 590 | +The use of default arguments allows the function call to be relatively simple in most cases, while still allowing the user to do more complex formatting if need be. |
| 591 | + |
| 592 | +From this example, we can also directly extrapolate a use case for building the arguments of a function in a dictionary. |
| 593 | +Imagine that you write an own function that uses one of the plotting methods of \matplotlib. |
| 594 | +Let's say that your function does a plot call where it provides ten parameter values. |
| 595 | +However, you have one special case where you need to provide one more parameter, maybe a line dash style that you otherwise do not need to provide. |
| 596 | +Then, you could have some \pythonil{if} in your code that branches to do the ten-parameter-call in one case and the eleven-parameter-call in the other. |
| 597 | +This means that a rather complex function call appears twice in a very similar manner. |
| 598 | +If you instead construct the parameters in a dictionary and in the \pythonil{if} branch just add the eleventh parameter if need be, your code will become much simpler.% |
| 599 | +% |
| 600 | +\FloatBarrier% |
| 601 | +\endhsection% |
| 602 | +% |
467 | 603 | \endhsection%
|
468 | 604 | %
|
0 commit comments