{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 函数也是数据:初级篇" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "前面我们已经提过一次,**函数也是一种对象**。\n", "\n", "或者说:**函数也是数据**(*function is data*),是差不多的。\n", "\n", "这是个重要的时刻,从现在开始我们对函数的理解将有一个新的飞跃。函数虽然是操作数据的工具,但它自己也是个数据对象,我们可以对它进行各种各样的操作,甚至在运行时动态的构造出一个函数来,这些都是 Python(以及很多流行的现代化编程语言)的亮点。我们在这一部分的最后还会就这个话题进行展开,从而引入一系列非常有用的“**函数式**(*functional*)”编程工具,在目前这一章我们先稍微品尝一下这个概念,来看看函数别名和匿名函数。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 函数别名" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "在 Python 中,所有函数也是对象,证据就是它们都有对象 id。Python 会为创建的每一个对象(不管基本数据类型,还是某个 *class* 的实例)指定一个唯一的 id,可以用内置函数 `id()` 来查看,比如:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4356419792" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n = 42\n", "id(n)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "函数也有这个 id,比如:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def _is_leap(year):\n", " return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4394147424" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(_is_leap)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "既然函数有 id,是个对象,那是什么类型的对象呢?可以用内置函数 `type` 来看:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "function" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(_is_leap)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "所以函数是个 `function` 类型的对象。\n", "\n", "既然是个对象,我们就可以用赋值语句来创建函数的**别名**(*alias*):" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "is_leap = _is_leap" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "4394147424" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(is_leap)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "可以看到,这两个函数的 id 完全一样,是同一个对象的两个名字而已。我们可以用这两个名字来调用这个函数,完全没区别:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "_is_leap(2018)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "is_leap(2018)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "那么,我们为什么需要给函数取别名呢?\n", "\n", "很多时候是为了提供更好的代码可读性,比如在特定上下文让某个函数的作用更显而易见,比如以前的例子里,我们曾经在 `Cat` 类里给父类 `Animal` 的 `voice()` 方法定义别名叫 `meow()`。\n", "\n", "还有一种情况是一个函数需要在运行时动态指向不同的实现版本。这里只简单描述一个典型场景:假定我们要渲染一段视频,如果系统里有兼容的显卡(GPU),就调用显卡来渲染,会更快更省电,如果没有则用 CPU 来渲染,会慢一点和更耗电一点,于是我们把用 GPU 渲染的算法写成函数 `_render_by_gpu()`,用 CPU 渲染的算法写成函数 `_render_by_cpu()`,而检测是否存在可用 GPU 的算法写成函数 `is_gpu_available()`,然后可以用下面的方法来定义一个函数 `render`:\n", "\n", "```python\n", "if is_gpu_available():\n", " render = _render_by_gpu\n", "else:\n", " render = _render_by_cpu\n", "```\n", "\n", "这样 `render()` 就成为一个当前系统中最优化的渲染函数,在程序的其他地方就不用管细节,直接用这个函数就好。这就是动态函数别名的价值。\n", "\n", "顺便说一句,在任何一个工程里,为函数或者变量取名都是**很简单却不容易**的事情——因为可能会重名(虽然已经尽量用变量的作用域隔离了),可能会因取名含混而令后来者费解,所以,仅仅为了少敲几下键盘而给一个函数取个更短的别名,实际上并不是好主意,更不是好习惯。尤其现在的编辑器都支持自动补全和多光标编辑的功能,变量名长点不是什么大问题。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 匿名函数" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "有的函数需要两个甚至更多名字,有的函数却一个也不要,人生就是这么丰富多彩啊!\n", "\n", "所谓匿名函数,就是有时候我们需要一个函数,但就在一个地方,用完就扔,再也不会用了,Python 对这种情况提供了一个方便的语法,不需要 `def` 那套严肃完整的语法,一行就可以写完一个函数,这个语法使用关键字 `lambda`。`lambda` 是希腊字母 `λ` 的英语音译,在计算机领域是个来头不小的词儿,代表了一系列高深的理论,[和阿伦佐·丘奇(Alonzo Church)的理论有关](https://en.wikipedia.org/wiki/Lambda_calculus),有兴趣的话可以自行研究。\n", "\n", "不过目前我们不需要管那么多,只要了解怎么快速创建“用过即扔”的小函数就好了。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "比如下面这个很简单的函数:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 9, "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": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add = lambda x, y: x + y\n", "add(3, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "甚至更简单一点,名字也不要了:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "8" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "(lambda x, y: x + y)(3, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "最后这种形式,就是典型的匿名函数了。简单地说,`lambda` 可以生成一个函数对象,出现在所有需要一个函数的地方,可以将其赋给一个变量(如上面的 `add`),这个变量就称为函数变量(别名),可以当函数用;也可以直接把 `lambda` 语句用括号括起来当一个函数用(上面后一种形式)。\n", "\n", "在 Python 官方文档中,`lambda` 语句的语法定义是这样的:\n", "\n", "`lambda_expr ::= \"lambda\" [parameter_list] \":\" expression`\n", "\n", "这个语法定义采用的是 [巴克斯范式(BNF)](https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_form)标注,现在不明白没关系(虽然对照上面的例子也能猜出个大概吧),以后我们会专门介绍。\n", "\n", "其实也很简单,就是这个样子:\n", "\n", "```python\n", "lambda x, y: x + y\n", "```\n", "\n", "先写上 `lambda` 关键字,其后分为两个部分,`:` 之前是参数表,之后是表达式,这个表达式的值,就是这个函数的返回值。**注意**:`lambda` 语句中,`:` 之后有且只能有一个表达式,所以它搞不出很复杂的函数,比较适合一句话的函数。\n", "\n", "而这个函数呢,没有名字,所以被称为 “匿名函数”。\n", "\n", "`add = lambda x, y: x + y`\n", "\n", "就相当于是给一个没有名字的函数取了个名字。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "我们在后面的学习中会遇到很多 `lambda` 的例子,因为有很多地方需要这种就用一次而且一句话的小函数,目前大家只要对这个概念和语法有所理解并且记住就可以了。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 小结" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 函数也是对象,有 *id*,是 `function` 类型;\n", "* 所以函数也可以被赋值给一个变量,把那个变量变成自己的别名(*alias*);\n", "* 可以用 `lambda` 来创建一次性、一句话的小函数,在很多场景下都很有用。" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 4 }