From 669c8aeb20ef7a97207f9e82087cc7d0e4392c2f Mon Sep 17 00:00:00 2001 From: lifulin <3057561137@qq.com> Date: Sun, 30 Jun 2024 12:03:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=20/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 函数.ipynb | 603 ++++++++++++++++++++++++ 化名与匿名.ipynb | 565 +++++++++++++++++++++++ 参数.ipynb | 1139 ++++++++++++++++++++++++++++++++++++++++++++++ 流程控制.ipynb | 652 ++++++++++++++++++++++++++ 递归.ipynb | 473 +++++++++++++++++++ 5 files changed, 3432 insertions(+) create mode 100644 函数.ipynb create mode 100644 化名与匿名.ipynb create mode 100644 参数.ipynb create mode 100644 流程控制.ipynb create mode 100644 递归.ipynb diff --git a/函数.ipynb b/函数.ipynb new file mode 100644 index 0000000..6763819 --- /dev/null +++ b/函数.ipynb @@ -0,0 +1,603 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "函数,实际上是可被调用的完整的程序。它具备输入、处理、输出的功能。\n", + "\n", + "函数包含两个方面:\n", + "\n", + "> * 它的**输入**是怎么构成的(都有哪些参数?如何指定?);\n", + "> * 以及它的**输出**是什么(返回值究竟是什么?)……\n", + "\n", + "```python\n", + "def cow(grass)\n", + " return milk\n", + "```\n", + "\n", + "我们使用函数的过程中,我们常常*有意忽略*它的内部如何完成从输入到输出之间的*处理过程* —— 这就好像我们平日里用灯泡一样,大多数情况下,我们只要知道开关的使用方法就够了 —— 至于为什么按到这个方向上灯会亮,为什么按到另外一个方向上灯会灭,并不是我们作为用户必须关心的事情……\n", + "\n", + "当然,如果你是设计开关的人就不一样了,你必须知道其中的运作原理;但是,最终,你还是希望你的用户用最简单方便的操作界面,而不是必须搞懂所有原理才能够使用你所设计的产品……\n", + "\n", + "当我们用 Python 编程的时候,更多的情况下,我们只不过是在使用别人已经写好的函数." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "toc-hr-collapsed": true + }, + "source": [ + "## 示例 print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 基本的使用方法" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "它最基本的作用就是把传递给它的值输出到屏幕上,如果不给它任何参数,那么它就输出一个空行:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "line 1st\n", + "line 2nd\n", + "\n", + "line 4th\n" + ] + } + ], + "source": [ + "print('line 1st')\n", + "print('line 2nd')\n", + "print()\n", + "print('line 4th')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "也可以传递多个参数,参数之间用 `,` 分开,它就会把那些值逐个输出到屏幕,每个值之间默认用空格分开。" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, jack mike ... and all you guys!\n" + ] + } + ], + "source": [ + "print('Hello,', 'jack', 'mike', '...', 'and all you guys!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当我们想把变量或者表达式的值插入字符串中的时候,可以用 f-string:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ann is 22 years old.\n" + ] + } + ], + "source": [ + "name = 'Ann'\n", + "age = '22'\n", + "print(f'{name} is {age} years old.')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但这并不是 `print()` 这个函数的功能,这实际上是 [`f-string`](https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting) 的功能,`f-string` 中用花括号 `{}` 括起来的部分是表达式,最终转换成字符串的时候,那些表达式的值(而不是变量或者表达式本身)会被插入相应的位置……" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Ann is 22 years old.'" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "name = 'Ann'\n", + "age = '22'\n", + "f'{name} is {age} years old.'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所以,`print(f'{name} is {age} years old.')` 这一句中,函数 `print()` 完成的还是它最基本的功能:给它什么,它就把什么输出到屏幕上。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### print() 的官方文档说明" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "最必须读懂的部分,就是这一行:\n", + "\n", + "> `print(*object, sep=' ', end='\\n', file=sys.stdout, flush=False)` [1]\n", + "\n", + "先只注意那些有着 `=` 的参数,`sep=' '`、`end='\\n'`\n", + "\n", + "这其中,先关注这三个 `sep=' '`、`end='\\n'`\n", + "\n", + "> * `sep=' '`:接收多个参数之后,输出时,分隔符号默认为空格,`' '`;\n", + "> * `end='\\n'`:输出行的末尾默认是换行符号 `'\\n'`;\n", + "\n", + "也就是说,这个函数中有若干个具有默认值的参数,即便我们在调用这个函数的时候,就算没有指定它们,它们也存在于此。\n", + "\n", + "即,当我们调用 `print('Hello', 'world!')` 的时候,相当于我们调用的是 `print('Hello', 'world!', sep=' ', end='\\n', file=sys.stdout, flush=False)`" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello world!\n", + "Hello-world!\tHello~world!\n", + "Hello\n", + "world!\n" + ] + } + ], + "source": [ + "print('Hello', 'world!') # 下一行的输出和这一行相同\n", + "print('Hello', 'world!', sep='-', end='\\t')\n", + "print('Hello', 'world!', sep='~') # 上一行的末尾是 \\t,所以,这一行并没有换行显示\n", + "print('Hello', 'world!', sep='\\n') # 参数之间用换行 \\n 分隔 " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> `print()` 这个函数的返回值是 `None` —— 注意,它向屏幕输出的内容,与 `print()` 这个函数的返回值不是一回事。\n", + "\n", + "做为例子,看看 `print(print(1))` 这个语句 —— `print()` 这个函数被调用了两次,第一次是 `print(1)`,它向屏幕输出了一次,完整的输出值实际上是 `str(1) + '\\n'`,而后返回一个值,`None`;而第二次调用 print(),这相当于是向屏幕输出这个 `None`:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n" + ] + } + ], + "source": [ + "print(1)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "None\n" + ] + } + ], + "source": [ + "print(print(1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 关键字参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Python 中,函数的参数,有两种:\n", + "> * **位置参数**(Positional Arguments,在官方文档里常被缩写为 *arg*)\n", + "> * **关键字参数**(Keyword Arguments,在官方文档里常被缩写为 *kwarg*)\n", + "\n", + "在函数定义中,带有 `=` 的,即,已为其设定了默认值的参数,叫做 Keyword Arguments,其它的是 Positional Arguments。\n", + "\n", + "在调用有 Keyword Arguments 的函数之时,如若不提供这些参数,那么参数在执行时,启用的是它在定义的时候为那些 Keyword Arguments 所设定的默认值;如若提供了这些参数的值,那么参数在执行的时候,启用的是接收到的相应值。\n", + "\n", + "比如,`sorted()` 函数,它的定义如下:\n", + "\n", + "> `sorted(iterable, *, key=None, reverse=False)`\n", + "\n", + "现在先只关注它的 Keyword Arguments,`reverse`:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['a', 'b', 'c', 'd']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "['d', 'c', 'b', 'a']" + ] + }, + "execution_count": 31, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "sorted('abdc')\n", + "sorted('abdc', reverse=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 位置参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "它接收且必须接收两个参数。\n", + "\n", + "> * 当你调用这个函数的时候,括号里写的第一个参数,是被除数,第二个参数是除数 —— 此为该函数的输入;\n", + "> * 而它的返回值,是一个元组(Tuple,至于这是什么东西,后面讲清楚),其中包括两个值,第一个是商,第二个是余 —— 此为该函数的输出。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 2)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(0, 3)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "(5, 0)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "divmod(11, 3)\n", + "a, b = divmod(11, 3)\n", + "a\n", + "b\n", + "\n", + "divmod(3, 11)\n", + "a, b = divmod(3, 11)\n", + "a\n", + "b\n", + "\n", + "tup = divmod(15 , 3)\n", + "tup\n", + "tup[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "toc-hr-collapsed": false + }, + "source": [ + "## 可选位置参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有些函数,如 `pow()`,有**可选的位置参数**(Optional Positional Arguments)。\n", + "\n", + "于是,`pow()` 有两种用法,各有不同的结果:\n", + "\n", + "> * `pow(x, y)` —— 返回值是 `x ** y`\n", + "> * `pow(x, y, z)` —— 返回值是 `x ** y % z`\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "pow(2, 3)\n", + "pow(2, 3, 4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Class 也是函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "虽然你现在还不一定知道 Class 究竟是什么,但在阅读官方文档的时候,遇到一些内建函数前面写着 Class,比如 `Class bool([x])`,千万别奇怪,因为 Class 本质上来看就是一种特殊类型的函数,也就是说,它也是函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "bool()\n", + "bool(3.1415926)\n", + "bool(-3.1415926)\n", + "bool(1 == 2)\n", + "bool(None)" + ] + } + ], + "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.8.2" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/化名与匿名.ipynb b/化名与匿名.ipynb new file mode 100644 index 0000000..d7be8a1 --- /dev/null +++ b/化名与匿名.ipynb @@ -0,0 +1,565 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 化名与匿名" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 化名" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 Python 中,我们可以给一个函数取个**化名**(alias)。\n", + "\n", + "以下的代码,我们先是定义了一个名为 `_is_leap` 的函数,而后为它另取了一个化名:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "4379191184" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "4379191184" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "function" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "function" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "def _is_leap(year):\n", + " return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n", + "\n", + "year_leap_bool = _is_leap\n", + "year_leap_bool #\n", + "year_leap_bool(800) # _is_leap(800) -> True\n", + "\n", + "id(year_leap_bool) # id() 这个函数可以查询某对象的内存地址\n", + "id(_is_leap) # year_leap_bool 和 _is_leap 其实保存在同一个地址中,也就是说,它们是同一个对象。\n", + "\n", + "type(year_leap_bool)\n", + "type(_is_leap) # 它们都是 function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们可以看到的是,`id(year_leap_bool)` 和 `id(_is_leap)` 的内存地址是一样的 —— 它们是同一个对象,它们都是函数。所以,当你写 `year_leap_bool = _is_leap` 的时候,相当于给 `_is_leap()` 这个函数取了个化名。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## lambda" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "写一个很短的函数可以用 `lambda` 关键字。\n", + "\n", + "下面是用 `def` 关键字写函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def add(x, y):\n", + " return x + y\n", + "add(3, 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面是用 `lambda` 关键字写函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "8" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "add = lambda x, y: x + y\n", + "add(3, 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "lambda 的语法结构如下:\n", + "\n", + "> `lambda_expr ::= \"lambda\" [parameter_list] \":\" expression`\n", + "\n", + "```python\n", + "lambda x, y: x + y\n", + "```\n", + "\n", + "先写上 `lambda` 这个关键字,其后分为两个部分,`:` 之前是参数,之后是表达式;这个表达式的值,就是这个函数的返回值。\n", + "\n", + "> **注意**:`lambda` 语句中,`:` 之后有且只能有一个表达式。\n", + "\n", + "而这个函数呢,没有名字,所以被称为 “匿名函数”。\n", + "\n", + "`add = lambda x, y: x + y`\n", + "\n", + "就相当于是给一个没有名字的函数取了个名字。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## lambda 的使用场景" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "那 lambda 这种匿名函数的用处在哪里呢?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 作为某函数的返回值" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第一个常见的用处是*作为另外一个函数的返回值*。\n", + "\n", + "让我们看看 [The Python Tutorial](https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions) 中的一个例子。" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "43" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def make_incrementor(n):\n", + " return lambda x: x + n\n", + "\n", + "f = make_incrementor(42)\n", + "f(0)\n", + "\n", + "f(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这个例子乍看起来很令人迷惑。我们先看看 `f = make_incrementor(42)` 之后,`f` 究竟是什么东西:" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ".(x)>" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "4428443296" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "4428726888" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def make_incrementor(n):\n", + " return lambda x: x + n\n", + "\n", + "f = make_incrementor(42)\n", + "f\n", + "\n", + "id(make_incrementor)\n", + "id(f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "首先,要注意,`f` 并不是 `make_incrementor()` 这个函数的化名,如果是给这个函数取个化名,写法应该是:\n", + "\n", + "```python\n", + "f = make_incrementor\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "那 `f` 是什么呢?它是 `.(x)>`:\n", + "\n", + "> * `f = make_incrementor(42)` 是将 `make_incrementor(42)` 的返回值保存到 `f` 这个变量之中;\n", + "> * 而 `make_incrementor()` 这个函数接收到 `42` 这个参数之后,返回了一个函数:`lambda x: x + 42`;\n", + "> * 于是,`f` 中保存的函数是 `lambda x: x + 42`;\n", + "> * 所以,`f(0)` 是向这个匿名函数传递了 `0`,而后,它返回的是 `0 + 42`。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 作为某函数的参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以拿一些可以接收函数为参数的内建函数做例子。比如,[`map()`](https://docs.python.org/3/library/functions.html#map)。\n", + "\n", + "> `map`(*function*, *iterable*, *...*)\n", + "> \n", + "> Return an iterator that applies *function* to every item of *iterable*, yielding the results. If additional *iterable* arguments are passed, *function* must take that many arguments and is applied to the items from all iterables in parallel. With multiple iterables, the iterator stops when the shortest iterable is exhausted. For cases where the function inputs are already arranged into argument tuples, see [`itertools.starmap()`](https://docs.python.org/3/library/itertools.html#itertools.starmap)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`map()` 这个函数的第一个参数,就是用来接收函数的。随后的参数,是 `iterable` —— 就是可被迭代的对象,比如,各种容器,例如:列表、元组、字典什么的。" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 4, 6, 8, 10, 12]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[2, 4, 6, 8, 10, 12]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def double_it(n):\n", + " return n * 2\n", + "\n", + "a_list = [1, 2, 3, 4, 5, 6]\n", + "\n", + "b_list = list(map(double_it, a_list))\n", + "b_list\n", + "\n", + "c_list = list(map(lambda x: x * 2, a_list))\n", + "c_list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "显然用 `lambda` 更为简洁。另外,类似完成 `double_it(n)` 这种简单功能的函数,常常有 “用过即弃” 的必要。" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'name': 'john', 'phone': 9876},\n", + " {'name': 'mike', 'phone': 5603},\n", + " {'name': 'stan', 'phone': 6898},\n", + " {'name': 'eric', 'phone': 7898}]" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "['john', 'mike', 'stan', 'eric']" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "[9876, 5603, 6898, 7898]" + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "phonebook = [\n", + " {\n", + " 'name': 'john',\n", + " 'phone': 9876\n", + " },\n", + " {\n", + " 'name': 'mike',\n", + " 'phone': 5603\n", + " },\n", + " {\n", + " 'name': 'stan',\n", + " 'phone': 6898\n", + " },\n", + " {\n", + " 'name': 'eric',\n", + " 'phone': 7898\n", + " }\n", + "]\n", + "\n", + "phonebook\n", + "list(map(lambda x: x['name'], phonebook))\n", + "list(map(lambda x: x['phone'], phonebook))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以给 map() 传递若干个可被迭代对象:" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 12, 30]" + ] + }, + "execution_count": 63, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a_list = [1, 3, 5]\n", + "b_list = [2, 4, 6]\n", + "\n", + "list(map(lambda x, y: x * y, a_list, b_list))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以上的例子都弄明白了,再去看 [The Python Tutorial](https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions) 中的例子,就不会有任何疑惑了:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]\n", + "pairs.sort(key=lambda p: p[1])\n", + "pairs" + ] + } + ], + "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.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/参数.ipynb b/参数.ipynb new file mode 100644 index 0000000..67e78d5 --- /dev/null +++ b/参数.ipynb @@ -0,0 +1,1139 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "this is **string** example....wow!!!\n" + ] + } + ], + "source": [ + "#!/usr/bin/python3\n", + "\n", + " \n", + "exp = \"*****this is **string** example....wow!!!*****\"\n", + "print (exp.strip( '*' )) # 指定字符串 *" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3abcrunoob3\n" + ] + } + ], + "source": [ + "exp = \"123abcrunoob321\"\n", + "print (exp.strip( '21' )) # 字符序列为 21" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3abcrunoob3\n" + ] + } + ], + "source": [ + "def strip(input_string, chars=None):\n", + " if chars is None:\n", + " # 如果没有指定chars参数,默认为去除空格\n", + " chars = \" \\t\\n\\r\"\n", + " start = 0\n", + " end = len(input_string)\n", + "\n", + " # 从字符串开头去除指定的字符\n", + " while start < end and input_string[start] in chars:\n", + " start += 1\n", + "\n", + " # 从字符串结尾去除指定的字符\n", + " while start < end and input_string[end - 1] in chars:\n", + " end -= 1\n", + "\n", + " # 返回去除开头和结尾指定字符的字符串\n", + " return input_string[start:end]\n", + "\n", + "exp = \"123abcrunoob321\"\n", + "print (exp.strip( '12' )) # 字符序列为 12" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 关于参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "之前就提到过,从结构上来看,每个函数都是一个完整的程序,因为一个程序,核心构成部分就是输入、处理、输出:\n", + "\n", + "> * 它可以有**输入** —— 即,它能接收外部通过参数传递的值;\n", + "> * 它可以有**处理** —— 即,内部有能够完成某一特定任务的代码;尤其是,它可以根据 “输入” 得到 “输出”;\n", + "> * 它可以有**输出** —— 即,它能向外部输送返回值……" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 为函数取名" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "哪怕一个函数内部什么都不干,它也得有个名字,然后名字后面要加上圆括号 `()`,以明示它是个函数,而不是某个变量。\n", + "\n", + "定义一个函数的关键字是 `def`,以下代码定义了一个什么都不干的函数:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "def do_nothing():\n", + " pass\n", + "\n", + "do_nothing()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "为函数取名(为变量取名也一样)有些基本的注意事项:\n", + "\n", + "> - 首先,名称不能以数字开头。能用在名称开头的有,大小写字母和下划线 `_`;\n", + "> \n", + "> - 其次,名称中不能有空格,要么使用下划线连接词汇,如,`do_nothing`,要么使用 [Camel Case](https://en.wikipedia.org/wiki/Camel_case),如 `doNothing` —— 更推荐使用下划线;\n", + "> \n", + "> - 再次,名称不能与关键字重合 —— 以下是 Python 的 Keyword List:\n", + "\n", + "| - | Python | Keyword | List | - |\n", + "| ---------- | ---------- | ---------- | ---------- | ---------- |\n", + "| `and` | `as` | `assert` | `async` | `await` |\n", + "| `break` | `class` | `continue` | `def` | `del` |\n", + "| `elif` | `else` | `except` | `False` | `finally` |\n", + "| `for` | `from` | `global` | `if` | `import` |\n", + "| `in` | `is` | `lambda` | `None` | `nonlocal` |\n", + "| `not` | `or` | `pass` | `raise` | `return` |\n", + "| `True` | `try` | `while` | `with` | `yield` |\n", + "\n", + "你随时可以用以下代码查询关键字列表:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['False',\n", + " 'None',\n", + " 'True',\n", + " 'and',\n", + " 'as',\n", + " 'assert',\n", + " 'async',\n", + " 'await',\n", + " 'break',\n", + " 'class',\n", + " 'continue',\n", + " 'def',\n", + " 'del',\n", + " 'elif',\n", + " 'else',\n", + " 'except',\n", + " 'finally',\n", + " 'for',\n", + " 'from',\n", + " 'global',\n", + " 'if',\n", + " 'import',\n", + " 'in',\n", + " 'is',\n", + " 'lambda',\n", + " 'nonlocal',\n", + " 'not',\n", + " 'or',\n", + " 'pass',\n", + " 'raise',\n", + " 'return',\n", + " 'try',\n", + " 'while',\n", + " 'with',\n", + " 'yield']" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "import keyword\n", + "keyword.kwlist # 列出所有关键字\n", + "keyword.iskeyword('if') # 查询某个词是不是关键字" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 不接收任何参数的函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在定义函数的时候,可以定义成不接收任何参数;但调用函数的时候,依然需要写上函数名后面的圆括号 `()`:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is a hello message from do_something().\n" + ] + } + ], + "source": [ + "def do_something():\n", + " print('This is a hello message from do_something().')\n", + "\n", + "do_something()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 没有 return 语句的函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "函数内部,不一定非要有 `return` 语句 —— 上面 `do_somthing()` 函数就没有 `return` 语句。但如果函数内部并未定义返回值,那么,该函数的返回值是 `None`,当 `None` 被当作布尔值对待的时候,相当于是 `False`。\n", + "\n", + "这样的设定,使得函数调用总是可以在条件语句中被当作判断依据:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is a hello message from do_something().\n", + "This is a hello message from do_something().\n", + "The return value of 'do_something()' is None.\n", + "The return value of 'do_something()' is None.\n" + ] + } + ], + "source": [ + "def do_something():\n", + " print('This is a hello message from do_something().')\n", + "\n", + "if do_something():\n", + " print(\"The return value of 'do_something()' is None.\")\n", + "\n", + "if not do_something(): # 由于该函数名称的缘故,这一句代码的可读性很差……\n", + " print(\"The return value of 'do_something()' is None.\")\n", + "\n", + "if True:\n", + " print(\"The return value of 'do_something()' is None.\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`if not do_something(): ` 翻译成自然语言,应该是,“如果 `do_something()` 的返回值是 ‘非真’,那么:……” " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 接收外部传递进来的值" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "让我们写个判断闰年年份的函数,取名为 `is_leap()`,它接收一个年份为参数,若是闰年,则返回 `True`,否则返回 `False`。\n", + "\n", + "根据闰年的定义:\n", + "\n", + "> * 年份应该是 4 的倍数;\n", + "> * 年份能被 100 整除但不能被 400 整除的,不是闰年。\n", + "\n", + "所以,相当于要在能被 4 整除的年份中,排除那些能被 100 整除却不能被 400 整除的年份。" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def is_leap(year):\n", + " leap = False\n", + " if year % 4 == 0:\n", + " leap = True\n", + " if year % 100 == 0 and year % 400 != 0:\n", + " leap = False\n", + " return leap\n", + "\n", + "\n", + "is_leap(7)\n", + "is_leap(12)\n", + "is_leap(100)\n", + "is_leap(400)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 另外一个更为简洁的版本,理解它还挺练脑子的\n", + "# cpython/Lib/datetime.py\n", + "def _is_leap(year):\n", + " return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n", + "_is_leap(300)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "函数可以同时接收多个参数。比如,我们可以写个函数,让它输出从大于某个数字到小于另外一个数字的斐波那契数列;那就需要定义两个参数,调用它的时候也需要传递两个参数:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "144 233 377 610 987 1597 2584 4181 6765 " + ] + } + ], + "source": [ + "def fib_between(start, end):\n", + " a, b = 0, 1\n", + " while a < end:\n", + " if a >= start:\n", + " print(a, end=' ')\n", + " a, b = b, a + b\n", + " \n", + "fib_between(100, 10000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当然可以把这个函数写成返回值是一个列表:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def fib_between(start, end):\n", + " r = []\n", + " a, b = 0, 1\n", + " while a < end:\n", + " if a >= start:\n", + " r.append(a)\n", + " a, b = b, a + b\n", + " return r\n", + " \n", + "fib_between(100, 10000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 变量的作用域" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "下面的代码,经常会让初学者迷惑:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n" + ] + } + ], + "source": [ + "def increase_one(n):\n", + " n += 1\n", + " return n\n", + "\n", + "n = 1\n", + "print(increase_one(n))\n", + "# print(n)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当 `increase_one(n)` 被调用之后,`n` 的值究竟是多少呢?或者更准确点问,随后的 `print(n)` 的输出结果应该是什么呢?\n", + "\n", + "输出结果是 `1`。\n", + "\n", + "在程序执行过程中,变量有**全局变量**(Global Variables)和**局域变量**(Local Variables)之分。\n", + "\n", + "> 首先,每次某个函数被调用的时候,这个函数会开辟一个新的区域,这个函数内部所有的变量,都是局域变量。也就是说,即便那个函数内部某个变量的名称与它外部的某个全局变量名称相同,它们也不是同一个变量 —— 只是名称相同而已。\n", + "> \n", + "> 其次,更为重要的是,当外部调用一个函数的时候,准确地讲,传递的不是变量,而是那个变量的*值*。也就是说,当 `increase_one(n)` 被调用的时候,被传递给那个恰好名称也叫 `n` 的局域变量的,是全局变量 `n` 的值,`1`。\n", + "> \n", + "> 而后,`increase_one()` 函数的代码开始执行,局域变量 `n` 经过 `n += 1` 之后,其中存储的值是 `2`,而后这个值被 `return` 语句返回,所以,`print(increase(n))` 所输出的值是函数被调用之后的返回值,即,`2`。\n", + "> \n", + "> 然而,全局变量 `n` 的值并没有被改变,因为局部变量 `n`(它的值是 `2`)和全局变量 `n`(它的值还是 `1`)只不过是名字相同而已,但它们并不是同一个变量。\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**例外:** 不过,有一种情况要格外注意 —— 在函数内部处理被传递进来的值是**可变容器**(比如,列表)的时候:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, ['What?!', 2, 3])" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def be_careful(a, b):\n", + " a = 2\n", + " b[0] = 'What?!'\n", + "\n", + "a = 1\n", + "b = [1, 2, 3]\n", + "be_careful(a, b)\n", + "a, b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "所以,一个比较好的习惯是,如果传递进来的值是列表,那么在函数内部对其操作之前,先创建一个它的拷贝:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, [1, 2, 3])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def be_careful(a, b):\n", + " a = 2\n", + " b_copy = b.copy()\n", + " b_copy[0] = 'What?!'\n", + "\n", + "a = 1\n", + "b = [1, 2, 3]\n", + "be_careful(a, b)\n", + "a, b" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 可以接收一系列值的位置参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果你在定义参数的时候,在一个*位置参数*(Positional Arguments)前面标注了星号,`*`,那么,这个位置参数可以接收**一系列值**(简单来说,不止一个参数),在函数内部可以对这一系列值用 `for ... in ...` 循环进行逐一的处理。\n", + "\n", + "带一个星号的参数,英文名称是 “Arbitrary Positional Arguments”,姑且翻译为 “随意的位置参数”。\n", + "\n", + "还有带两个星号的参数,一会儿会讲到,英文名称是 “Arbitrary Keyword Arguments”,姑且翻译为 “随意的关键字参数”。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, ann!\n", + "Hi, mike!\n", + "Hi, john!\n", + "Hi, zeo!\n" + ] + } + ], + "source": [ + "def say_hi(*names):\n", + " for name in names:\n", + " print(f'Hi, {name}!')\n", + "say_hi()\n", + "say_hi('ann')\n", + "say_hi('mike', 'john', 'zeo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`say_hi()` 这一行没有任何输出。因为你在调用函数的时候,没有给它传递任何值,于是,在函数内部代码执行的时候,`name in names` 的值是 `False`,所以,`for` 循环内部的代码没有被执行。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在函数内部,是把 `names` 这个参数当作容器处理的 —— 否则也没办法用 `for ... in ...` 来处理。而在调用函数的时候,我们是可以将一个容器传递给函数的 Arbitrary Positional Arguments 的 —— 做法是,在调用函数的时候,在参数前面加上星号 `*`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, mike!\n", + "Hi, john!\n", + "Hi, zeo!\n" + ] + } + ], + "source": [ + "def say_hi(*names):\n", + " for name in names:\n", + " print(f'Hi, {name}!')\n", + "\n", + "names = ('mike', 'john', 'zeo')\n", + "say_hi(*names)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "实际上,因为以上的 `say_hi(*names)` 函数内部就是把接收到的参数当作容器处理的,于是,在调用这个函数的时候,向它传递任何容器都会被同样处理:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, P!\n", + "Hi, y!\n", + "Hi, t!\n", + "Hi, h!\n", + "Hi, o!\n", + "Hi, n!\n", + "Hi, 0!\n", + "Hi, 1!\n", + "Hi, 2!\n", + "Hi, 3!\n", + "Hi, 4!\n", + "Hi, 5!\n", + "Hi, 6!\n", + "Hi, 7!\n", + "Hi, 8!\n", + "Hi, 9!\n", + "Hi, 10!\n", + "Hi, 9!\n", + "Hi, 8!\n", + "Hi, 7!\n", + "Hi, 6!\n", + "Hi, 5!\n", + "Hi, 4!\n", + "Hi, 3!\n", + "Hi, 2!\n", + "Hi, 1!\n", + "Hi, ann!\n", + "Hi, mike!\n", + "Hi, joe!\n" + ] + } + ], + "source": [ + "def say_hi(*names):\n", + " for name in names:\n", + " print(f'Hi, {name}!')\n", + "\n", + "a_string = 'Python'\n", + "say_hi(*a_string)\n", + "\n", + "a_range = range(10)\n", + "say_hi(*a_range)\n", + "\n", + "a_list = list(range(10, 0, -1))\n", + "say_hi(*a_list)\n", + "\n", + "a_dictionary = {'ann':2321, 'mike':8712, 'joe': 7610}\n", + "say_hi(*a_dictionary)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "_在定义可以接收一系列值的位置参数时,建议在函数内部为该变量命名时总是用**复数**_,因为函数内部,总是需要 `for` 循环去迭代元组中的元素,这样的时候,名称的复数形式对代码的可读性很有帮助." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**注意**:一个函数中,可以接收一系列值的位置参数只能有一个;并且若是还有其它位置参数存在,那就必须把这个可以接收一系列值的位置参数排在所有其它位置参数之后。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, Mike!\n", + "Hello, John!\n", + "Hello, Zeo!\n" + ] + } + ], + "source": [ + "def say_hi(greeting, *names):\n", + " for name in names:\n", + " print(f'{greeting}, {name.capitalize()}!')\n", + "\n", + "say_hi('Hello', 'mike', 'john', 'zeo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 为函数的某些参数设定默认值" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以在定义函数的时候,为某些参数设定默认值,这些有默认值的参数,又被称作关键字参数(Keyword Arguments)。从这个函数的 “用户” 角度来看,这些设定了默认值的参数,就成了 “可选参数”。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, mike!\n", + "Hello, john!\n", + "Hello, zeo!\n", + "Hello, Mike!\n", + "Hello, John!\n", + "Hello, Zeo!\n" + ] + } + ], + "source": [ + "def say_hi(greeting, *names, capitalized=False):\n", + " for name in names:\n", + " if capitalized:\n", + " name = name.capitalize()\n", + " print(f'{greeting}, {name}!')\n", + "\n", + "say_hi('Hello', 'mike', 'john', 'zeo')\n", + "say_hi('Hello', 'mike', 'john', 'zeo', capitalized=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "可以在定义函数的时候,为某些参数设定默认值,这些有默认值的参数,又被称作关键字参数(Keyword Arguments)。从这个函数的 “用户” 角度来看,这些设定了默认值的参数,就成了 “可选参数”。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 可以接收一系列值的关键字参数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "之前我们看到,可以设定一个位置参数(Positional Argument),接收一系列的值,被称作 “Arbitrary Positional Argument”;\n", + "\n", + "同样地,我们也可以设定一个可以接收很多值的关键字参数(Arbitrary Keyword Argument)。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, mike!\n", + "Oh, my darling, ann!\n", + "Hi, john!\n" + ] + } + ], + "source": [ + "def say_hi(**names_greetings):\n", + " for name, greeting in names_greetings.items():\n", + " print(f'{greeting}, {name}!')\n", + " \n", + "say_hi(mike='Hello', ann='Oh, my darling', john='Hi')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "既然在函数内部,我们在处理接收到的 Arbitrary Keyword Argument 时,用的是对字典的迭代方式,那么,在调用函数的时候,也可以直接使用字典的形式:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, mike!\n", + "Oh, my darling, ann!\n", + "Hi, john!\n", + "Hello, mike!\n", + "Oh, my darling, ann!\n", + "Hi, john!\n" + ] + } + ], + "source": [ + "def say_hi(**names_greetings):\n", + " for name, greeting in names_greetings.items():\n", + " print(f'{greeting}, {name}!')\n", + " \n", + "a_dictionary = {'mike':'Hello', 'ann':'Oh, my darling', 'john':'Hi'}\n", + "say_hi(**a_dictionary)\n", + "\n", + "say_hi(**{'mike':'Hello', 'ann':'Oh, my darling', 'john':'Hi'})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "至于在函数内部,你用什么样的迭代方式去处理这个字典,是你自己的选择:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, mike!\n", + "Oh, my darling, ann!\n", + "Hi, john!\n" + ] + } + ], + "source": [ + "def say_hi_2(**names_greetings):\n", + " for name in names_greetings:\n", + " print(f'{names_greetings[name]}, {name}!')\n", + "say_hi_2(mike='Hello', ann='Oh, my darling', john='Hi')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 函数定义时各种参数的排列顺序" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, mike!\n", + "Hi, john!\n", + "Hi, zeo!\n", + "Welcome, Mike!\n", + "Welcome, John!\n", + "Welcome, Zeo!\n" + ] + } + ], + "source": [ + "def say_hi(greeting, *names, capitalized=False):\n", + " for name in names:\n", + " if capitalized:\n", + " name = name.capitalize()\n", + " print(f'{greeting}, {name}!')\n", + "\n", + "say_hi('Hi', 'mike', 'john', 'zeo')\n", + "say_hi('Welcome', 'mike', 'john', 'zeo', capitalized=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果,你想给其中的 `greeting` 参数也设定个默认值怎么办?写成这样好像可以:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi, mike!\n", + "Hi, john!\n", + "Hi, zeo!\n", + "\n", + "Welcome, Mike!\n", + "Welcome, John!\n", + "Welcome, Zeo!\n" + ] + } + ], + "source": [ + "def say_hi(greeting='Hello', *names, capitalized=False):\n", + " for name in names:\n", + " if capitalized:\n", + " name = name.capitalize()\n", + " print(f'{greeting}, {name}!')\n", + "\n", + "say_hi('Hi', 'mike', 'john', 'zeo')\n", + "say_hi('Welcome', 'mike', 'john', 'zeo', capitalized=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "但 `greeting` 这个参数虽然有默认值,可这个函数在被调用的时候,还是必须要给出这个参数,否则输出结果出乎你的想象:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "mike, john!\n", + "mike, zeo!\n" + ] + } + ], + "source": [ + "def say_hi(greeting='Hello', *names, capitalized=False):\n", + " for name in names:\n", + " if capitalized:\n", + " name = name.capitalize()\n", + " print(f'{greeting}, {name}!')\n", + "\n", + "say_hi('mike', 'john', 'zeo')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "设定了默认值的 `greeting`,竟然不像你想象的那样是 “可选参数”!所以,你得这样写:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello, mike!\n", + "Hello, john!\n", + "Hello, zeo!\n", + "Hi, mike!\n", + "Hi, john!\n", + "Hi, zeo!\n" + ] + } + ], + "source": [ + "def say_hi(*names, greeting='Hello', capitalized=False):\n", + " for name in names:\n", + " if capitalized:\n", + " name = name.capitalize()\n", + " print(f'{greeting}, {name}!')\n", + "\n", + "say_hi('mike', 'john', 'zeo')\n", + "say_hi('mike', 'john', 'zeo', greeting='Hi')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "这是因为函数被调用时,面对许多参数,Python 需要按照既定的规则(即,顺序)判定每个参数究竟是哪一类型的参数:\n", + "\n", + "> **Order of Arguments**\n", + "> 1. Positional\n", + "> 1. Arbitrary Positional\n", + "> 1. Keyword\n", + "> 1. Arbitrary Keyword\n", + "\n", + "所以,即便你在定义里写成\n", + "\n", + "```python\n", + "def say_hi(greeting='Hello', *names, capitalized=False):\n", + " ...\n", + "```\n", + "\n", + "在调用该函数的时候,无论你写的是\n", + "```python\n", + "say_hi('Hi', 'mike', 'john', 'zeo')\n", + "```\n", + "\n", + "还是\n", + "```python\n", + "say_hi('mike', 'john', 'zeo')\n", + "```\n", + "\n", + "Python 都会认为接收到的第一个值是 Positional Argument —— 因为在定义中,`greeting` 被放到了 Arbitrary Positional Arguments 之前。" + ] + } + ], + "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.11.4" + }, + "toc-autonumbering": true + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/流程控制.ipynb b/流程控制.ipynb new file mode 100644 index 0000000..d6e43de --- /dev/null +++ b/流程控制.ipynb @@ -0,0 +1,652 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 流程控制" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "无论多么复杂的流程,归根到底就两点:`分支`与`循环`\n", + "```python\n", + "for n in range(2, 100):\n", + " if n == 2:\n", + " print(n)\n", + " continue\n", + " for i in range(2, n):\n", + " if (n % i) == 0:\n", + " break\n", + " else:\n", + " print(n) \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## if 语句" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`if` 语句的最简单构成是这样 —— 注意第 1 行末尾的冒号 `:` 和第 2 行的缩进:\n", + "\n", + "```python\n", + "if expression:\n", + " statements\n", + "```\n", + "\n", + "如果表达式 `expression` 返回值为真,执行 `if` 语句块内部的 `statements`,否则,什么都不做,执行 `if` 之后的下一个语句。" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "r = random.randrange(1, 1000)\n", + "\n", + "if r % 2 == 0:\n", + " print(f'{r} is even.')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "如果,表达式 `expression` 返回值无论真假,我们都需要做一点相应的事情,那么我们这么写:\n", + "\n", + "```python\n", + "if expression:\n", + " statements_for_True\n", + "else:\n", + " statements_for_False\n", + "```\n", + "\n", + "如果表达式 `expression` 返回值为真,执行 `if` 语句块内部的 `statements_for_True`,否则,就执行 `else` 语句块内部的 `statements_for_False`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "945 is odd.\n" + ] + } + ], + "source": [ + "import random\n", + "r = random.randrange(1, 1000)\n", + "\n", + "if r % 2 == 0:\n", + " print(f'{r} is even.')\n", + "else:\n", + " print(f'{r} is odd.')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**但是,可以简化!**" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "even\n" + ] + } + ], + "source": [ + "import random\n", + "r = random.randrange(1, 1000)\n", + "\n", + "def EvenOdd(n):\n", + " if n % 2 == 0:\n", + " return(\"even\")\n", + " return(\"odd\")\n", + "\n", + "print(EvenOdd(r))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有时,表达式 `` 返回的值有多种情况,并且针对不同的情况我们都要做相应的事情,那么可以这么写:\n", + "\n", + "```python\n", + "if expression_1:\n", + " statements_for_expression_1_True\n", + " \n", + "elif expression_2:\n", + " statements_for_expression_2_True\n", + "\n", + "elif expression_3:\n", + " statements_for_expression_3_True\n", + "\n", + "elif expression_...:\n", + " statements_for_expression_..._True\n", + "```\n", + "\n", + "`elif` 是 `else if` 的缩写,作用相同。\n", + "\n", + "以下程序模拟投两个骰子的结果 —— 两个骰子数字加起来,等于 `7` 算平,大于 `7` 算大,小于 `7` 算小:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Big!\n" + ] + } + ], + "source": [ + "import random\n", + "r = random.randrange(2, 13)\n", + "\n", + "if r == 7:\n", + " print('Draw!')\n", + "elif r < 7:\n", + " print('Small!')\n", + "elif r > 7:\n", + " print('Big!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当然你还可以模拟投飞了的情况,即,最终的骰子数是 `0` 或者 `1`,即,`< 2`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Small!\n" + ] + } + ], + "source": [ + "import random\n", + "r = random.randrange(0, 13) # 生成的随机数应该从 0 开始了;\n", + "\n", + "if r == 7:\n", + " print('Draw!')\n", + "elif r >= 2 and r < 7: # 如果这里直接写 elif r < 7:,那么,else: 那一部分永远不会被执行……\n", + " print('Small!')\n", + "elif r > 7:\n", + " print('Big!')\n", + "else:\n", + " print('Not valid!')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## for 循环" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "value of a: 0\n", + "value of a: 1\n", + "value of a: 2\n", + "value of a: 3\n", + "value of a: 4\n", + "value of a: 5\n", + "value of a: 6\n", + "value of a: 7\n", + "value of a: 8\n", + "value of a: 9\n" + ] + } + ], + "source": [ + "for a in range(10):\n", + " print(f'value of a: {a}') #每次 a 的值都不同,从 0 递增至 9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### range() 函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`range()` 是个内建函数,[它的文档](https://docs.python.org/3/library/functions.html#func-range)是这样写的:\n", + "\n", + "> **range**(_stop_) \n", + ">\n", + "> **range**(_start, stop[, step]_)\n", + "\n", + "只有一个参数的时候,这个参数被理解为 `stop`,生成一个从 `0` 开始,到 `stop - 1` 的整数数列。\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"all\"\n", + "\n", + "range(10)\n", + "list(range(10)) # 将 range(10) 转换成 list,以便清楚看到其内容。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`start` 参数的默认值是 `0`。如需指定起点,那么得给 `range()` 传递两个参数,比如,`range(2, 13)`……" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(range(2, 13))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "第三个参数可选;`step`,步长,就相当于是 “等差数列” 当中的 “差”,默认值是 `1`。例如,`range(1, 10, 2)` 生成的是这样一个数列 `[1, 3, 5, 7, 9]`。所以,打印 `0 ~ 10` 之间的所有奇数,可以这样写:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "3\n", + "5\n", + "7\n", + "9\n" + ] + } + ], + "source": [ + "for i in range(1, 10, 2):\n", + " print(i)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "我们也可以生成负数的数列:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list(range(0, -10, -1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Continue、Break 和 Pass" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在循环的过程中,还可以用 `continue` 和 `break` 控制流程走向,通常是在某条件判断发生的情况下 —— 正如你早就见过的那样:\n", + "\n", + "```python\n", + "for n in range(2, 100):\n", + " if n == 2:\n", + " print(n)\n", + " continue\n", + " for i in range(2, n):\n", + " if (n % i) == 0:\n", + " break\n", + " else:\n", + " print(n) \n", + "```\n", + "\n", + "`continue` 语句将忽略其后的语句开始下次循环,而 `break` 语句将从此结束当前循环.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`pass` 语句什么都不干:\n", + "\n", + "再比如,\n", + "```python\n", + "def someFunction():\n", + " pass\n", + "```\n", + "\n", + "又或者:\n", + "\n", + "```python\n", + "for i in range(100):\n", + " pass\n", + " if i % 2 == 0:\n", + " pass\n", + "```\n", + "\n", + "`pass` 这个语句更多是给写程序的人用的。当你写程序的时候,你可以用 `pass` 占位,而后先写别的部分,过后再回来补充本来应该写在 `pass` 所在位置的那一段代码。\n", + "\n", + "写嵌套的判断语句或循环语句的时候,最常用 `pass`,因为写嵌套挺费脑子的,一不小心就弄乱了。所以,经常需要先用 `pass` 占位,而后逐一突破。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## while 循环" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "今天,在绝大多数编程语言中,都提供两种循环结构:\n", + "\n", + "> * Collection-controlled loops(以集合为基础的循环)\n", + "> * Condition-controlled loops(以条件为基础的循环)\n", + "\n", + "之前的 `for ... in ...` 就是 Collection-controlled loops;而在 Python 中提供的 Condition-controlled loops 是 `while` 循环。\n", + "\n", + "`while` 循环的格式如下:\n", + "\n", + "```python\n", + "while expression:\n", + " statements\n", + "```\n", + "\n", + "输出 1000 以内的斐波那契数列的程序如下:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 \n" + ] + } + ], + "source": [ + "n = 1000\n", + "a, b = 0, 1\n", + "while a < n:\n", + " print(a, end=' ')\n", + " a, b = b, a+b\n", + "print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`for` 和 `while` 的区别在哪里?什么时候应该用哪个?\n", + "\n", + "`for` 更适合处理序列类型的数据(Sequence Type)的迭代,比如处理字符串中的每一个字符,比如把 `range()` 返回的数列当作某种序列类型的索引。\n", + "\n", + "`while` 更为灵活,因为它后面只需要接上一个逻辑表达式即可。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 一个投骰子赌大小的游戏" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以下是一个让用户和程序玩掷骰子赌大小的程序。规则如下:\n", + "\n", + ">* 每次计算机随机生成一个 `2... 12` 之间的整数,用来模拟机器人投两个骰子的情况;\n", + "* 机器人和用户的起始资金都是 10 个硬币\n", + "* 要求用户猜大小:\n", + " * 用户输入 `b` 代表 “大”;\n", + " * 用户输入 `s` 代表 “小”;\n", + " * 用户输入 `q` 代表 “退出”;\n", + "* 用户的输入和随机产生的数字比较有以下几种情况:\n", + " * 随机数小于 `7`,用户猜小,用户赢;\n", + " * 随机数小于 `7`,用户猜大,用户输;\n", + " * 随机数等于 `7`,用户无论猜大还是猜小,结局平,不输不赢;\n", + " * 随机数大于 `7`,用户猜小,用户输;\n", + " * 随机数大于 `7`,用户猜大,用户赢;\n", + "* 游戏结束条件:\n", + " * 机器人和用户,若任意一方硬币数量为 `0`,则游戏结束;\n", + " * 用户输入了 `q` 主动终止游戏。\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "You: 10\t Bot: 10\n", + "The dice is 12;\n", + "You WIN!\n", + "\n", + "You: 11\t Bot: 9\n", + "The dice is 9;\n", + "You WIN!\n", + "\n", + "You: 12\t Bot: 8\n", + "The dice is 9;\n", + "You WIN!\n", + "\n", + "You: 13\t Bot: 7\n", + "The dice is 8;\n", + "You WIN!\n", + "\n", + "You: 14\t Bot: 6\n", + "The dice is 8;\n", + "You WIN!\n", + "\n", + "You: 15\t Bot: 5\n", + "The dice is 8;\n", + "You WIN!\n", + "\n", + "You: 16\t Bot: 4\n" + ] + } + ], + "source": [ + "from random import randrange\n", + "\n", + "coin_user, coin_bot = 10, 10 # 可以用一个赋值符号分别为多个变量赋值\n", + "rounds_of_game = 0\n", + "\n", + "def bet(dice, wager): # 接收两个参数,一个是骰子点数,另一个用户的输入\n", + " if dice == 7:\n", + " print(f'The dice is {dice};\\nDRAW!\\n') # \\n 是换行符号\n", + " return 0\n", + " elif dice < 7:\n", + " if wager == 's':\n", + " print(f'The dice is {dice};\\nYou WIN!\\n')\n", + " return 1\n", + " else:\n", + " print(f'The dice is {dice};\\nYou LOST!\\n')\n", + " return -1\n", + " elif dice > 7:\n", + " if wager == 's':\n", + " print(f'The dice is {dice};\\nYou LOST!\\n')\n", + " return -1\n", + " else:\n", + " print(f'The dice is {dice};\\nYou WIN!\\n')\n", + " return 1\n", + "\n", + "while True: # 除 for 之外的另外一个循环语句\n", + " print(f'You: {coin_user}\\t Bot: {coin_bot}')\n", + " dice = randrange(2, 13) # 生成一个 2 到 12 的随机数\n", + " wager = input(\"What's your bet? \")\n", + " if wager == 'q':\n", + " break \n", + " elif wager in 'bs': # 只有当用户输入的是 b 或者 s 得时候,才 “掷骰子”……\n", + " result = bet(dice, wager)\n", + " coin_user += result # coin_user += result 相当于 coin_user = coin_user + result\n", + " coin_bot -= result\n", + " rounds_of_game += 1\n", + " if coin_user == 0:\n", + " print(\"Woops, you've LOST ALL, and game over!\")\n", + " break\n", + " elif coin_bot == 0:\n", + " print(\"Woops, the robot's LOST ALL, and game over!\")\n", + " break\n", + " \n", + "print(f\"You've played {rounds_of_game} rounds.\\n\")\n", + "print(f\"You have {coin_user} coins now.\\nBye!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 总结" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "有控制流,才能算得上是程序。\n", + "\n", + "> * 只处理一种情况,用 `if ... `\n", + "> * 处理 `True`/`False` 两种情况,用 `if ... else ...`\n", + "> * 处理多种情况,用 `if ... elif ... elif ... else ...`\n", + "> * 迭代有序数据类型,用 `for ... in ...`,如果需要处理没有 `break` 发生的情况,用 `for ... else ...`\n", + "> * 其它循环,用 `while ...`\n", + "> * 与循环相关的语句还有 `continue`、`break`、`pass`\n", + "> * 函数从控制流角度去看其实就是子程序" + ] + } + ], + "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.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/递归.ipynb b/递归.ipynb new file mode 100644 index 0000000..7142b0e --- /dev/null +++ b/递归.ipynb @@ -0,0 +1,473 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 递归函数" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 递归(Recursion)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在函数中有个理解门槛比较高的概念:**递归函数**(Recursive Functions)—— 那些**在自身内部调用自身的函数**。说起来都比较拗口。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在所有的项目中,都是非常常用的,这样子写,虽然运算复杂了、逻辑性要求更高,但是那是计算机考虑的事情,对人而言,写的代码少了,预计的结果出来了!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "先看一个例子,我们想要有个能够计算 `n` 的*阶乘*(factorial)`n!` 的函数,`f()`,规则如下:\n", + "\n", + "> - `n! = n × (n-1) × (n-2)... × 1`\n", + "> - 即,`n! = n × (n-1)!`\n", + "> - 且,`n >= 1`\n", + ">\n", + "> **注意**:以上是数学表达,不是程序,所以,`=` 在这一小段中是 “*等于*” 的意思,**不是程序语言中的赋值符号**。\n", + "\n", + "于是,计算 `f(n)` 的 Python 程序如下:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "120\n" + ] + } + ], + "source": [ + "def f(n):\n", + " if n == 1:\n", + " return 1\n", + " else:\n", + " return n * f(n-1)\n", + " \n", + "print(f(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 递归函数的执行过程" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "以 factorial(5) 为例,让我们看看程序的流程:\n", + "\n", + "![](images/recursive-function-call.png)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "当 f(5) 被调用之后,函数开始运行……\n", + "* 因为 `5 > 1`,所以,在计算 `n * f(n-1)` 的时候要再次调用自己 `f(4)`;所以必须等待 `f(4)` 的值返回;\n", + "* 因为 `4 > 1`,所以,在计算 `n * f(n-1)` 的时候要再次调用自己 `f(3)`;所以必须等待 `f(3)` 的值返回;\n", + "* 因为 `3 > 1`,所以,在计算 `n * f(n-1)` 的时候要再次调用自己 `f(2)`;所以必须等待 `f(2)` 的值返回;\n", + "* 因为 `2 > 1`,所以,在计算 `n * f(n-1)` 的时候要再次调用自己 `f(1)`;所以必须等待 `f(1)` 的值返回;\n", + "* 因为 `1 == 1`,所以,这时候不会再次调用 `f()` 了,于是递归结束,开始返回,这次返回的是 `1`;\n", + "* 下一步返回的是 `2 * 1`;\n", + "* 下一步返回的是 `3 * 2`;\n", + "* 下一步返回的是 `4 * 6`;\n", + "* 下一步返回的是 `5 * 24` —— 至此,外部调用 `f(5)` 的最终返回值是 `120`……\n", + "\n", + "加上一些输出语句之后,能更清楚地看到大概的执行流程:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Call f(5)...\n", + "\tn = 5\n", + "\tn = 4\n", + "\tn = 3\n", + "\tn = 2\n", + "\tn = 1\n", + "Returning...\n", + "\tn = 1 return: 1\n", + "\tn = 2 return: 2\n", + "\tn = 3 return: 6\n", + "\tn = 4 return: 24\n", + "\tn = 5 return: 120\n", + "Get out of f(n), and f(5) = 120\n" + ] + } + ], + "source": [ + "def f(n):\n", + " print('\\tn =', n)\n", + " if n == 1:\n", + " print('Returning...')\n", + " print('\\tn =', n, 'return:', 1)\n", + " return 1\n", + " else:\n", + " r = n * f(n-1)\n", + " print('\\tn =', n, 'return:', r)\n", + " return r\n", + " \n", + "print('Call f(5)...')\n", + "print('Get out of f(n), and f(5) =', f(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 递归的终点" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "递归函数在内部必须有一个能够让自己停止调用自己的方式,否则永远循环下去了……\n", + "\n", + "其实,我们所有人很小就见过递归应用,只不过,那时候不知道那就是递归而已。听过那个无聊的故事罢?\n", + "\n", + "> 山上有座庙,庙里有个和尚,和尚讲故事,说……\n", + "> > 山上有座庙,庙里有个和尚,和尚讲故事,说……\n", + "> > > 山上有座庙,庙里有个和尚,和尚讲故事,说……\n", + "\n", + "写成 Python 程序大概是这样:\n", + "\n", + "```python\n", + "def a_monk_telling_story():\n", + " print('山上有座庙,庙里有个和尚,和尚讲故事,他说…… ')\n", + " return a_monk_telling_story()\n", + "\n", + "a_monk_telling_story()\n", + "```\n", + "\n", + "这是个*无限循环*的递归,因为这个函数里*没有设置中止自我调用的条件*。无限循环还有个不好听的名字,叫做 “死循环”。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在著名的电影**盗梦空间**(_2010_)里,从整体结构上来看,“入梦” 也是个 “递归函数”。只不过,这个函数和 `a_monk_telling_story()` 不一样,它并不是死循环 —— 因为它设定了*中止自我调用的条件*:\n", + "\n", + "> 在电影里,醒过来的条件有两个\n", + ">> * 一个是在梦里死掉;\n", + ">> * 一个是在梦里被 kicked 到……\n", + ">\n", + "> 如果这两个条件一直不被满足,那就进入 limbo 状态 —— 其实就跟死循环一样,出不来了……\n", + "\n", + "为了演示,我把故事情节改变成这样:\n", + "> * 入梦,`in_dream()`,是个递归函数;\n", + "> * 入梦之后醒过来的条件有两个:\n", + ">> * 一个是在梦里死掉,`dead is True`;\n", + ">> * 一个是在梦里被 kicked,`kicked is True`……\n", + ">>\n", + ">> 以上两个条件中任意一个被满足,就苏醒……\n", + "\n", + "至于为什么会死掉,如何被 kick,我偷懒了一下:管它怎样,管它如何,反正,每个条件被满足的概率是 1/10……(也只有这样,我才能写出一个简短的,能够运行的 “*盗梦空间程序*”。)\n", + "\n", + "把这个很抽象的故事写成 Python 程序,看看一次入梦之后能睡多少天,大概是这样:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dead: False kicked: False\n", + "dead: False kicked: False\n", + "dead: False kicked: False\n", + "dead: True kicked: False\n", + "I slept 4 days, and was dead to wake up...\n", + "The in_dream() function returns: 4\n" + ] + } + ], + "source": [ + "import random\n", + "\n", + "def in_dream(day=0, dead=False, kicked=False):\n", + " dead = not random.randrange(0,10) # 1/10 probability to be dead\n", + " kicked = not random.randrange(0,10) # 1/10 probability to be kicked\n", + " day += 1\n", + " print('dead:', dead, 'kicked:', kicked)\n", + " \n", + " if dead:\n", + " print((f\"I slept {day} days, and was dead to wake up...\"))\n", + " return day\n", + " elif kicked:\n", + " print(f\"I slept {day} days, and was kicked to wake up...\")\n", + " return day\n", + " \n", + " return in_dream(day)\n", + " \n", + "print('The in_dream() function returns:', in_dream())" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "ename": "RecursionError", + "evalue": "maximum recursion depth exceeded", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRecursionError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mx\u001b[39m(n):\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m n \u001b[38;5;241m*\u001b[39m x(n\u001b[38;5;241m-\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m----> 3\u001b[0m \u001b[43mx\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m5\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[10], line 2\u001b[0m, in \u001b[0;36mx\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mx\u001b[39m(n):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m n \u001b[38;5;241m*\u001b[39m \u001b[43mx\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n", + "Cell \u001b[0;32mIn[10], line 2\u001b[0m, in \u001b[0;36mx\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mx\u001b[39m(n):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m n \u001b[38;5;241m*\u001b[39m \u001b[43mx\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n", + " \u001b[0;31m[... skipping similar frames: x at line 2 (2969 times)]\u001b[0m\n", + "Cell \u001b[0;32mIn[10], line 2\u001b[0m, in \u001b[0;36mx\u001b[0;34m(n)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mx\u001b[39m(n):\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m n \u001b[38;5;241m*\u001b[39m \u001b[43mx\u001b[49m\u001b[43m(\u001b[49m\u001b[43mn\u001b[49m\u001b[38;5;241;43m-\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m)\u001b[49m\n", + "\u001b[0;31mRecursionError\u001b[0m: maximum recursion depth exceeded" + ] + } + ], + "source": [ + "def x(n):\n", + " return n * x(n-1)\n", + "x(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "不用深究上面盗梦空间这个程序的其它细节,不过,通过以上三个递归程序 —— 两个很扯淡的例子,一个正经例子 —— 你已经看到了递归函数的共同特征:\n", + "\n", + "> 1. 在 `return` 语句中返回的是*自身的调用*(或者是*含有自身的表达式*)\n", + "> 2. 为了避免死循环,*一定要有至少一个条件*下返回的不再是自身调用……" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 变量的作用域" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "再回来看计算阶乘的程序 —— 这是正经程序。这次我们把程序名写完整,`factorial()`:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "120\n" + ] + } + ], + "source": [ + "def factorial(n):\n", + " if n == 1:\n", + " return 1\n", + " else:\n", + " return n * factorial(n-1)\n", + " \n", + "print(factorial(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "最初的时候,这个函数的执行流程之所以令人迷惑,是因为初学者对*变量*的**作用域**把握得不够充分。\n", + "\n", + "变量根据作用域,可以分为两种:全局变量(Global Variables)和局部变量(Local Variables)。\n", + "\n", + "可以这样简化理解:\n", + "\n", + "> * 在函数内部被赋值而后使用的,都是*局部变量*,它们的作用域是*局部*,无法被函数外的代码调用;\n", + "> * 在所有函数之外被赋值而后开始使用的,是*全局变量*,它们的作用域是*全局*,在函数内外都可以被调用。\n", + "\n", + "定义如此,但通常程序员们会严格地遵守一条原则:\n", + "\n", + "> 在函数内部绝对不调用全局变量。即便是必须改变全局变量,也只能通过函数的返回值在函数外改变全局变量。\n", + "\n", + "你也必须遵守同样的原则。而这个原则同样可以在日常的工作生活中 “调用”:\n", + "\n", + "> 做事的原则:自己的事自己做,别人的事,最多通过自己的产出让他们自己去搞……" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "再仔细观察一下以下代码。当一个变量被当做参数传递给一个函数的时候,这个变量本身并不会被函数所改变。比如,`a = 5`,而后,再把 `a` 当作参数传递给 `f(a)` 的时候,这个函数当然应该返回它内部任务完成之后应该传递回来的值,但 `a` 本身不会被改变。" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 120\n", + "120 120\n" + ] + } + ], + "source": [ + "def factorial(n):\n", + " if n == 1:\n", + " return 1\n", + " else:\n", + " return n * factorial(n-1)\n", + " \n", + "a = 5\n", + "b = factorial(a) # a 并不会因此改变;\n", + "print(a, b)\n", + "a = factorial(a) # 这是你主动为 a 再一次赋值……\n", + "print(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "理解了这一点之后,再看 `factorial()` 这个递归函数的递归执行过程,你就能明白这个事实:\n", + "\n", + "> 在每一次 factorial(n) 被调用的时候,它都会形成一个作用域,`n` 这个变量作为参数把它的值传递给了函数,*但是*,`n` 这个变量本身并不会被改变。\n", + "\n", + "我们再修改一下上面的代码:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 120\n" + ] + } + ], + "source": [ + "def factorial(n):\n", + " if n == 1:\n", + " return 1\n", + " else:\n", + " return n * factorial(n-1)\n", + " \n", + "n = 5 # 这一次,这个变量名称是 n\n", + "m = factorial(n) # n 并不会因此改变;\n", + "print(n, m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "在 `m = factorial(n)` 这一句中,`n` 被 `factorial()` 当做参数调用了,但无论函数内部如何操作,并不会改变变量 `n` 的值。\n", + "\n", + "关键的地方在这里:在函数内部出现的变量 `n`,和函数外部的变量 `n` 不是一回事 —— **它们只是名称恰好相同而已**,函数参数定义的时候,用别的名称也没什么区别:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 120\n" + ] + } + ], + "source": [ + "def factorial(x): # 在这个语句块中出现的变量,都是局部变量\n", + " if x == 1:\n", + " return 1\n", + " else:\n", + " return x * factorial(x-1)\n", + " \n", + "n = 5 # 这一次,这个变量名称是 n\n", + "m = factorial(n) # n 并不会因此改变;\n", + "print(n, m)\n", + "# 这个例子和之前再之前的示例代码有什么区别吗?\n", + "# 本质上没区别,就是变量名称换了而已……" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "函数开始执行的时候,`x` 的值,是由外部代码(即,函数被调用的那一句)传递进来的。即便函数内部的变量名称与外部的变量名称相同,它们也不是同一个变量。" + ] + } + ], + "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.8.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}