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