{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 函数\n",
    "\n",
    "{term}`函数 <function>` 可以向调用者返回某个值的语句。还可以向其传入零个或多个 {term}`参数 <argument>` 并在函数体执行中被使用。结构如下：\n",
    "\n",
    "```python\n",
    "def 函数名([参数]):\n",
    "    函数体\n",
    "```\n",
    "\n",
    "其中 `[参数]` 表示零个或多个 {term}`参数 <argument>`；`def` 用于声明此结构为函数，可以称之为 **函数定义**。\n",
    "\n",
    "```{important}\n",
    "函数被调用，总是返回值的。函数的返回值是借由 {token}`return 语句 <python-grammar:return_stmt>` （属于函数体的一部分）操作的。\n",
    "\n",
    "如果 `return` 的表达式的值是 {data}`None`，则称其所在函数为 **过程**。过程是可以省略 `return 语句` 的。\n",
    "```\n",
    "\n",
    "## 参数\n",
    "\n",
    "{term}`参数 <argument>` （可以称为 **实参**）即在调用函数时传给其的值。分为两种：\n",
    "\n",
    "关键字参数\n",
    ":   在函数调用中前面带有标识符（例如 `name=`）或者作为包含在前面带有 `**` 的字典里的值传入。\n",
    "\n",
    "    举例来说，`3` 和 `5` 在以下对 {class}`complex` 的调用中均属于关键字参数：\n",
    "\n",
    "    ```python\n",
    "    complex(real=3, imag=5)\n",
    "    complex(**{'real': 3, 'imag': 5})\n",
    "    ```\n",
    "\n",
    "位置参数\n",
    ":   不属于关键字参数的参数。位置参数可出现于参数列表的开头或者作为前面带有 `*` 的 {term}`可迭代对象 <iterable>` （即能够逐一返回其成员项的对象）里的元素被传入。\n",
    "\n",
    "    举例来说，`3` 和 `5` 在以下调用中均属于位置参数：\n",
    "\n",
    "    ```python\n",
    "    complex(3, 5)\n",
    "    complex(*(3, 5))\n",
    "    ```\n",
    "\n",
    "## 运算符\n",
    "\n",
    "运算符组成的表达式语句也可以看特殊的函数。比如数字加法：\n",
    "\n",
    "```python\n",
    "3 + 4\n",
    "```\n",
    "\n",
    "可以看作是：\n",
    "\n",
    "```python\n",
    "加法(3, 4)\n",
    "```\n",
    "\n",
    "## `print` 与 `input`\n",
    "\n",
    "函数 {func}`print` 与 {func}`input` 分别用于表示输出信息到屏幕、接受用户（使用你写的代码的“人”）的输入信息。\n",
    "\n",
    "比如，可以打印格式字符串输出信息："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "小马的身高是 185 厘米。\n"
     ]
    }
   ],
   "source": [
    "身高 = 185\n",
    "print(f'小马的身高是 {身高} 厘米。') # 下面会输出预期的信息"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "也可以获取用户的输入：\n",
    "\n",
    "```python\n",
    "年龄 = input('请输入您的年龄：')\n",
    "年龄\n",
    "```\n",
    "\n",
    "> '30'\n",
    "\n",
    "```{important}\n",
    "{func}`print` 仅仅打印求值结果，是一个过程，而 {func}`input` 是有实际返回值的。\n",
    "```\n",
    "\n",
    "## 形参\n",
    "\n",
    "函数定义中的命名实体，它指定函数可以接受的 {term}`参数 <argument>`。\n",
    "\n",
    "```{code-block}\n",
    "def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):\n",
    "      -----------    ----------     ----------\n",
    "        |             |                  |\n",
    "        |        Positional or keyword   |\n",
    "        |                                - Keyword only\n",
    "         -- Positional only\n",
    "```\n",
    "\n",
    "有五种形参：\n",
    "\n",
    "`positional-or-keyword`\n",
    ":   位置或关键字。指定一个可以作为 位置参数 或者 关键字参数 传入的实参。这是默认的形参类型。\n",
    "\n",
    "    例如下面的 `foo` 和 `bar`：\n",
    "\n",
    "    ```python\n",
    "    def func(foo, bar=None): ...\n",
    "    ```\n",
    "\n",
    "`positional-only`\n",
    ":   仅限位置。指定一个只能通过位置传入的参数。仅限位置形参可通过在函数定义的形参列表中包含一个 `/` 字符来定义。\n",
    "\n",
    "    例如下面的 `posonly1` 和 `posonly2`：\n",
    "\n",
    "    ```python\n",
    "    def func(posonly1, posonly2, /, positional_or_keyword): ...\n",
    "    ```\n",
    "\n",
    "`keyword-only`\n",
    ":   仅限关键字。指定一个只能通过关键字传入的参数。仅限关键字形参可通过在函数定义的形参列表中包含单个可变位置形参或者在多个可变位置形参之前放一个 `*` 来定义。\n",
    "\n",
    "    例如下面的 `kw_only1` 和 `kw_only2`：\n",
    "\n",
    "    ```python\n",
    "    def func(arg, *, kw_only1, kw_only2): ...\n",
    "    ```\n",
    "\n",
    "`var-positional`\n",
    ":   可变位置。指定可以提供由一个任意数量的位置参数构成的序列（附加在其他形参已接受的位置参数之后）。这种形参可通过在形参名称前加缀 `*` 来定义。\n",
    "\n",
    "    例如下面的 `args`：\n",
    "\n",
    "    ```python\n",
    "    def func(*args, **kwargs): ...\n",
    "    ```\n",
    "\n",
    "`var-keyword`\n",
    ":   可变关键字。指定可以提供任意数量的关键字参数（附加在其他形参已接受的关键字参数之后）。这种形参可通过在形参名称前加缀 `**` 来定义。\n",
    "\n",
    "    例如上面的 `kwargs`。\n",
    "\n",
    "关键字形参也可称为 **命名形参**。\n",
    "\n",
    "```{note}\n",
    "使用仅限位置形参，可以让用户无法使用形参名。形参名没有实际意义时，强制调用函数的实参顺序时，或同时接收位置形参和关键字时，这种方式很有用。\n",
    "\n",
    "当形参名有实际意义，且显式名称可以让函数定义更易理解时，阻止用户依赖传递实参的位置时，才使用关键字。\n",
    "\n",
    "对于 API，使用仅限位置形参，可以防止未来修改形参名时造成破坏性的 API 变动。\n",
    "```\n",
    "\n",
    "## 形参和实参之间有什么区别？\n",
    "\n",
    "{term}`形参 <parameter>` 是指出现在函数定义中的名称，而 {term}`实参 <argument>` 则是在调用函数时实际传入的值。{term}`形参 <parameter>` 定义了一个函数能接受何种类型的 {term}`实参 <argument>`。例如，对于以下函数定义：\n",
    "\n",
    "```python\n",
    "def func(foo, bar=None, **kwargs):\n",
    "    ...\n",
    "```\n",
    "\n",
    "`foo`、`bar` 和 `kwargs` 是 `func` 的形参。不过在调用 `func` 时，例如：\n",
    "\n",
    "```python\n",
    "func(42, bar=314, extra=somevar)\n",
    "```\n",
    "\n",
    "`42`、`314` 和 `somevar` 则是实参。\n",
    "\n",
    "## Lambda 表达式\n",
    "\n",
    "{keyword}`lambda` 关键字用于创建小巧的匿名函数。`lambda a, b: a+b` 函数返回两个参数的和。Lambda 函数可用于任何需要函数对象的地方。\n",
    "\n",
    "## 文档字符串\n",
    "\n",
    "文档字符串用于对函数做出解释，以方便使用该函数的人更快的理解该函数。\n",
    "\n",
    "文档字符串内容和格式的约定：\n",
    "\n",
    "- 第一行应为对象用途的简短摘要。为保持简洁，不要在这里显式说明对象名或类型，因为可通过其他方式获取这些信息（除非该名称碰巧是描述函数操作的动词）。（对于英语）这一行应以大写字母开头，以句点结尾。\n",
    "- 文档字符串为多行时，第二行应为空白行，在视觉上将摘要与其余描述分开。后面的行可包含若干段落，描述对象的调用约定、副作用等。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Do nothing, but document it.\n",
      "\n",
      "    No, really, it doesn't do anything.\n",
      "    \n"
     ]
    }
   ],
   "source": [
    "def my_function():\n",
    "    \"\"\"Do nothing, but document it.\n",
    "\n",
    "    No, really, it doesn't do anything.\n",
    "    \"\"\"\n",
    "    pass\n",
    "\n",
    "print(my_function.__doc__)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数的 `__doc__` 属性引用返回该函数的文档字符串。\n",
    "\n",
    "Python 解析器不会删除 Python 中多行字符串字面值的缩进，因此，文档处理工具应在必要时删除缩进。这项操作遵循以下约定：文档字符串第一行 之后 的第一个非空行决定了整个文档字符串的缩进量（第一行通常与字符串开头的引号相邻，其缩进在字符串中并不明显，因此，不能用第一行的缩进），然后，删除字符串中所有行开头处与此缩进“等价”的空白符。不能有比此缩进更少的行，但如果出现了缩进更少的行，应删除这些行的所有前导空白符。转化制表符后（通常为 8 个空格），应测试空白符的等效性。\n",
    "\n",
    "{guilabel}`视频`\n",
    "\n",
    "<iframe id=\"Python\"\n",
    "    title=\"Python 函数\"\n",
    "    width=\"100%\"\n",
    "    height=\"600\"\n",
    "    src=\"https://developer.hs.net/thread/1754\">\n",
    "</iframe>"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "681b3f0bf9b1b90d6d0c88f7c199d2184ced41189915c665b64d57703d01fa88"
  },
  "kernelspec": {
   "display_name": "Python 3.9.7 64-bit ('hs': conda)",
   "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.9.7"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
