xuzhengfu / pilot

进入编程世界的第一课
1 stars 0 forks source link

p2-5-functional-1 第五章 函数也是数据:初级篇 #35

Open xuzhengfu opened 4 years ago

xuzhengfu commented 4 years ago

1. 本章主旨

这是个重要的时刻,从现在开始我们对函数的理解将有一个新的飞跃。函数虽然是操作数据的工具,但它自己也是个数据对象,我们可以对它进行各种各样的操作,甚至在运行时动态地构造出一个函数来,这些都是 Python(以及很多流行的现代化编程语言)的亮点。我们在这一部分的最后还会就这个话题进行展开,从而引入一系列非常有用的 “函数式(functional)” 编程工具。

在目前这一章我们先稍微品尝一下这个概念,来看看 函数别名 和 匿名函数。

2. 函数别名

在 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)
4409611904
type(_is_leap)
function

所以函数是个 function 类型的对象。

既然是个对象,我们就可以用赋值语句来创建函数的别名(alias):

is_leap = _is_leap
id(is_leap)
4409611904

可以看到,这两个函数的 id 完全一样,是同一个对象的两个名字而已。我们可以用这两个名字来调用这个函数,完全没区别。

很多时候是为了提供更好的代码可读性,比如在特定上下文让某个函数的作用更显而易见,比如以前的例子里,我们曾经在 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() 就成为一个当前系统中最优化的渲染函数,在程序的其他地方就不用管细节,直接用这个函数就好。这就是 动态函数别名 的价值。

在任何一个工程里,为函数或者变量取名都是很简单却不容易的事情——因为可能会重名(虽然已经尽量用变量的作用域隔离了),可能会因取名含混而令后来者费解,所以,仅仅为了少敲几下键盘而给一个函数取个更短的别名,实际上并不是好主意,更不是好习惯。尤其现在的编辑器都支持 自动补全 和 多光标编辑 的功能,变量名长点不是什么大问题。

3. 匿名函数

所谓匿名函数,就是有时候我们需要一个函数,但就在一个地方,用完就扔,再也不会用了,Python 对这种情况提供了一个方便的语法,不需要 def 那套严肃完整的语法,一行就可以写完一个函数,这个语法使用关键字 lambda

lambda 是希腊字母 λ 的英语音译,在计算机领域是个来头不小的词儿,代表了一系列高深的理论,和阿伦佐·丘奇(Alonzo Church)的理论有关,有兴趣的话可以自行研究。

不过目前我们不需要管那么多,只要了解怎么快速创建 “用过即扔” 的小函数就好了。比如下面这个很简单的函数:

def add(x, y):
    return x + y
add(3, 5)

我们可以用 lambda 来改写:

add = lambda x, y: x + y
add(3, 5)

甚至更简单一点,名字也不要了:

(lambda x, y: x + y)(3, 5)

最后这种形式,就是典型的匿名函数了。简单地说,lambda 可以生成一个函数对象,出现在所有需要一个函数的地方,可以将其赋给一个变量(如上面的 add),这个变量就称为 函数变量(别名),可以当函数用;也可以直接把 lambda 语句用括号括起来当一个函数用(上面后一种形式)。

lambda_expr ::= "lambda" [parameter_list] ":" expression

这个语法定义采用的是 巴克斯范式(BNF)标注,现在不明白没关系(虽然对照上面的例子也能猜出个大概吧),以后我们会专门介绍。

其实也很简单,就是这个样子:

lambda x, y: x + y

先写上 lambda 关键字,其后分为两个部分,: 之前是 参数表,之后是 表达式,这个 表达式 的值,就是这个函数的 返回值。注意:lambda 语句中,: 之后 有且只能有 一个表达式,所以它搞不出很复杂的函数,比较适合一句话的函数。

这个函数没有名字,所以被称为 “匿名函数”。

add = lambda x, y: x + y

就相当于是给一个没有名字的函数取了个名字。

我们在后面的学习中会遇到很多 lambda 的例子,因为有很多地方需要这种就用一次而且一句话的小函数,目前大家只要对这个概念和语法有所理解并且记住就可以了。

小结

  • 函数也是对象,有 id,是 function 类型;
  • 所以函数也可以被赋值给一个变量,把那个变量变成自己的别名(alias);
  • 可以用 lambda 来创建一次性、一句话的小函数,在很多场景下都很有用。

Logging

2020-03-04 21:06:00 initialize