Python First-Class Functions 笔记 (头等函数)

Python First-Class Functions (头等函数) 的定义,我们应该如何理解以及如何应用。这篇笔记是在我看了 Dan Baber 的《Python Tricks: A Buffet of Awesome Python Features》的第三章时写下的。

然

Table of Contents

术语翻译

以下是本文所提到的英文术语翻译表,如有需要,可以点击展开。展开后可以使用 "Ctrl + F" 进行页面的搜寻。

本文所提到的英文术语翻译

argument — 实际参数(实参)
attribute — 属性
built-in method — 内置函数
callable — 可调用
closures/lexical closures — 闭包
data structures — 数据结构
decorators — 装饰器
first-class functions — 头等函数
first-class objects — 一等对象/第一类对象
function — 函数
function name — 函数名称
function object — 函数对象/函数物件
functional programming — 函数式编程
free variable — 自由变量
higher-order functions — 高阶函数
identifier — 标识符
indexing — 索引
inner function — 内部函数
iterable — 可迭代对象
list — 列表
method — 方法

nested function — 嵌套函数
object — 对象
object instance — 对象实例
parameter — 形式参数(形参)
parent function — 父级函数
string — 字符串
terminal — 终端
variable — 变量

简介

这篇笔记是在我看 Dan Bader 的《Python Tricks: A Buffet of Awesome Python Features》时写下的,主要是理解 Decorators (装饰器) 之前要先懂 First-Class Functions(头等函数)。

说实话我看这个篇章之前对 First-Class Function 没有什么印象,可能有学过但我给忘了。虽然一直有用 lambda 之类的东西,但我对这些概念一直不是特别清楚。

(这篇文章在23年9月份网站搬到Ghost之后,我有重新阅读并修改一下。)

First-Class Objects

其次,要理解 First-Class Function,我们需要先知道什么是 First-Class Objects(也叫 First-Class Citizen,头等对象),根据维基百科的定义:

In a given programming language design, a first-class citizen[a] is an entity which supports all the operations generally available to other entities.

意味着 First-Class Objects 有四个特性:

  • 可以将其 assign 到 variable
  • 将其存到 data structures
  • 将其作为 argument 传到其他 function
  • 将其作为 value 从 function 中 return

什么样的编程语言视为有First-Class Functions?

如果一个编程语言将 Functions 当作 First-Class Objects,则我们称这个编程语言为有 First-Class Functions,例如 Python,PHP,和 Javascript。

First-Class Functions

Python Function 可以被 Assign 到 Variable

先写一个叫 yell 的函数,用来把英文字符串变为大写:

$ python
>>> def yell(text):
...     return text.upper()
...
>>> yell("Hello")
'HELLO'

(因为我在 terminal 里写,所以定义函数的时候,左边有三个点)

接着我们让 shout = yell

>>> shout = yell

>>> shout("Hello")
'HELLO'

>>> yell("Hello")
'HELLO'

你会看到此时 shout 和 yell 都是可以使用的。

因为它相当于是创造了另一个叫 shout 的名字,然后同时指向 yell 连着的这个 funcion object。

我们要知道 function objects 和 function names 是两个分开的事物。

可以通过以下的操作进一步理解:

>>> del yell

>>> yell("Hello")
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'yell' is not defined

>>> shout("Hello")
'HELLO'

你会发现我们把 yell 这个名字删掉后,还是可以通过 shout 使用我们原本的 function object。

不过 Python 会给每个刚创造的 function 加一个 identifier ,也就是 __name__ 这个 attribute,默认是创建时的 function name,所以如果我们试着:

>>> shout.__name__
'yell'

会发现 shout 的名字还是 "yell"

Python Function 可以被存储到 Data Structures

我们可以创建一个叫 funcs 的 list,来存储各种函数:

>>> funcs = [shout, str.lower, str.capitalize]

>>> funcs
[<function yell at 0x1100fc578>, <method 'lower' of 'str' objects>, <method 'capitalize' of 'str' objects>]

(这里的 str.lower 是让一个 string 里所有的字符变成小写,而 str.capitalize 是让一个 string 里的第一个字符变成大写)

我们可以直接通过 indexing 调用里面的函数:

>>> funcs[2]("hello world")
'Hello world'

# 这里调用了 funcs 里面的 str.capitalize

Python Function 可以作为 Argument 被传入其他的 Functions

我们这里再写一个名为 greet 的函数,greet 可以接收其它 function 作为 arguments。

>>> def greet(func):
...     result = func("HEY you")
...     print(result)
...

greet 这里的作用就是,接受一个给定的 function,再给这个 function 传入一个为 "HEY you" 的 string 让它进行处理,最后将结果打印出来。

比如我们这里试着传入 shout:

>>> greet(shout)
HEY YOU

可以看到输出结果就是全大写的 "HEY YOU"。

我们当然也可以定义一个不一样的 whisper function 来传进去:

>>> def whisper(text):
...     return text.lower()
...

>>> greet(whisper)
hey you

Python 这样的性质是很牛逼的,你会发现在这里 greet 一直保持不变,但却因为我们传入的 function 不同而影响了它的行为。

这种可以接受其它 functions 作为 arguments 或者可以 return 一个 function 的 function 叫做 higher-order functions。如果你学过 functional programming,你应该是听过这样的概念。

Python 中的我们常用的 map function 就是 higher-order function,它可以接受一个 function object 和 一个 iterable,然后对这个 iterable 的每一个元素进行 function object 的操作。

举个例子:

def square(x):
	return x**2

result = list(map(square, [1,2,3,4]))

print(result)

# 输出:[1, 4, 9, 16]

Python Function 可以作为 Value 从 Function 中 Return

要理解这个特性,你首先要想一想我们什么时候需要从 function 中 return function。其实 First-Class Functions 的含义就在于将可以将 function 当作 variable 来看待。

那么我们平时从 function 中 return variable 是什么情况呢?一般就是这个 function 完成了某些计算,将计算结果存储在一个 variable,然后 return 这个 variable 出去。

所以 Python 需要 return function 的情况也类似,我们的用 function 做了一些计算,根据结果生成一个新的 function,接着将这个新的 function return 出去。

那么我们就需要知道 Python Function 有个重要的特质:

Python Function 可以被 Nested

这个我看书之前真是完全不知道。。。

在 Python 的 function 里面是可以再定义一个 function 的:

def speak(text):
	def whisper(t):
		return t.lower()
	return whisper(text)

我们可以这样使用 speak 里面的 whisper:

>>> speak("Hello World")
'hello world'

但要注意,我们是没有办法直接使用或者通过 speak 调用 whisper 的,也就是说 whisper 在出了 speak 之外的地方是不存在的:

>>> whisper("Hey")
Traceback (most recent call last):
  File "", line 1, in
NameError: name 'whisper' is not defined

>>> speak.whisper
Traceback (most recent call last):
  File "", line 1, in
AttributeError: 'function' object has no attribute 'whisper'

这种的名称叫做 nested functions 或者是 inner functions

如果我们真的想要拿到 speak 里面的 whisper,我们当然就可以让 speak 返还给我们:

>>> def speak():
...     def whisper(t):
...             return t.lower()
...     return whisper
...
>>> speak_func = speak()
>>> speak_func("HEY!")
'hey!'

>>> speak()("HAHA!")
'haha!'

注意这里第二个 speak 后面是两个括号,因为这里 speak() 是 return 一个 whisper function,而第二个括号才是传入 whisper 的 argument。

当然你会说这样的 return 好像没什么意思,不如直接定义一个 whisper 然后调用。的确如此,但在下面这些情况的时候就不一定了。

>>>> def get_speak_func(style):
...     def whisper(text):
...             return text.lower()
...     def yell(text):
...             return text.upper()
...     if style == "whisper":
...             return whisper
...     elif style == "yell":
...             return yell
...

可以看到我们这里通过传入的 style 这个参数,来决定要 return 的 speak function。

>>> get_speak_func("whisper")
.whisper at 0x0000017EE201D5E0>
>>> get_speak_func("yell")
.yell at 0x0000017EE201D820>

而 First-Class Function 能做的还远不止这样,接下来我们要谈到一个进阶的问题,Closure。

Python Function 可以 Capture Local State

接下来的东西就真的十分有趣了。我们已经知道了 function 是可以包含并 return inner function 的。

但更有趣的在于,这些 inner function 甚至可以获得和携带到它 parent function 的一些 state 的信息。

我们来看以下这个例子:

>>> def get_speak_func(text,style):
...     def whisper():
...             return text.lower()
...     def yell():
...             return text.upper()
...     if style == "whisper":
...             return whisper
...     elif style == "yell":
...             return yell
...

>>> whisper_func = get_speak_func("Hey","whisper")
>>> whisper_func()
'hey'

这里与前面最大的区别在于,我们在 get_speak_func 的 parameter 里传入了 text。并且 whisper 和 yell 是没有 parameter 的,它们相当于在直接调用 get_speak_func 传入的 text parameter。

但是有趣的地方在于,当我们跑完:

>>> whisper_func = get_speak_func("Hey","whisper")

这一句时,get_speak_func 其实已经算完成了它的工作。你会觉得text 这个变量应该是只作用于调用 get_speak_func 的期间,毕竟它是属于这个 get_speak_func 这个 scope,在 get_speak_func 工作完成后它应该销毁。

可是在我们跑以下这句的时候:

>>> whisper_func()
'hey'

你会发现 whisper_func 其实调用了 text 这个变量,也就是说,在 whisper_func 被 return 出来的时候,它其实记住了 text 这个 variable。

这种情况叫做 Lexical Closures (或者 Closures),书中的的解释是:

A closure remembers the values from its enclosing lexical scopre even when the program flow is no longer in that scopre.

Stack Overflow 上这句解释我也觉得不错:

A closure is a persistent scope which holds on to local variables even after the code execution has moved out of that block.

我们上边的 text 在这种情况就被称为 free variable。Wiki的解释:

In computer programming, the term free variable refers to variables used in a function that are neither local variables nor parameters of that function.

也可以再看看另一个例子:

>>> def create_adder(n):
...     def add(x):
...             return x + n
...     return add
...

>>> add_3 = create_adder(3)
>>> add_7 = create_adder(7)

>>> add_3(2)
5
>>> add_7(3)
10

这里 create_adder(n) 的意思在于,我们传入一个 n,然后返还一个专门用来加 n 的 function,比如 add_3 就是一个专门用来计算加 3 的 function,add_3(2)的意思就是算算 3+2 是多少,add_3(5)的意思就是算算 3+5 是多少。

可以看到这里的 n 也是一个 free variable,因为 add_3 和 add_7 也是记住了一开始传入的 3 和 7,以便后面进行计算。

Objects 也可以变得像 Function

我们一直在强调,Python 里所有的 Function 都是 Objects,但是反过来是不对的,也就是说 Objects 不是 Functions。不过 Objects 也可以变得 callable,所以让你有时也能把它们当作 Function 来用。

说一个 Object callable 的意思是,我们可以用 "()" 以及传入一些 arguments。想让一个 Object 变得 callable,我们可以用 __call__ 这个 Python built-in method 实现。

比如书中提到的:

Class Adder:
	def __init__(self, n):
		self.n = n
	def __call__(self, x):
		return self.n + x

>>> add_3 = Adder(3)
>>> add_3(2)
5

我们知道,add_3 在这里叫做 object instance,而当我们用 add_3(2) 这种形式,像 call function 一样使用它的时候,Python 会去执行 Class 里面的 __call__ method

add_3(2) 相当于 add_3.__call__(2) 的缩写。当然了,对于那些没有在 Class 里定义 __call__ method 的 objects,自然就是不 callable 的。

Python 本身也提供一个 callable 函数来让你检查一个 object 是否是 callable 的。

>>> callable(add_3)
True
>>> callable(shout)
True
>>> callable("Hey")
False

图书推荐

写完这篇我才发现这本书有中文版,不过我是看的 Amazon 的 Kindle 版。我个人觉得,这本书的英文版很适合刚过渡读英文技术书籍的人。

因为这本书最初是起源于作者在 Twitter 上分享的 Python 技巧,然后引起了不小的反响,之后通过邮件的形式分发给读者,再最后写成了书籍。

你会发现相较于其它的英文技术书,这本书的语言风格更加直白和日常,而且作者很懂得如何循序渐进地让读者明白相关的概念,这篇文章的例子也多是改编或选自原书。

如果你看完这篇文章有兴趣购买这本书的话,可以使用我的联盟链接,已经是我找到的兼顾信用和优惠的商家:

参考

PythonPython Tricks头等函数 (First-Class Function)自由变量 (Free Variable)闭包 (closure)

Comments