在 Python 中, 函数是一等对象。 编程语言理论家把“一等对象”定义为满足四个条件的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
在 Python 中, 整数、 字符串和字典都是一等对象。 后面的课程将重点讨论把函数作为对象的影响和实际应用。
看一个小小的例子:
def factorial(n):
"""
returns n!
:param n:
:return:
"""
return 1 if n < 2 else n * factorial(n-1)
print(factorial.__doc__)
print(type(factorial))
把函数视作对象,Python函数是对象,我们先创建一个函数,然后读取它的一个属性,最后确定这个函数本身是 function 类的一个实例。
接下来呢,展示了函数对象的“一等”本性。 我们可以把 factorial 函数赋值给变量 fact,
然后通过变量名调用。 我们还能把它作为参数传给 map 函数。 map 函数返回一个可迭代
对象, 里面的元素是把第一个参数(一个函数) 应用到第二个参数(一个可迭代对象, 这
里是 range(11)
fact = factorial
print(fact)
print(fact(5))
print(map(factorial, range(11)))
print(list(map(fact, range(11))))
有了一等函数或者说是一等对象, 就可以使用函数式风格编程。 函数式编程的特点之一是使用高阶函数。
什么是高阶函数呢?
接受函数为参数, 或者把函数作为结果返回的函数是高阶函数。
最常见的 sorted 函数就是 高阶函数。
我们做一个简单的例子,根据 字符串 的长度排序。
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
print(sorted(fruits, key=len))
在函数式编程语言中,最常见的高阶函数有: map、 filter、 reduce, apply(在python3 中已经被删除了)。
三个问题:
- 将列表中每个元素扩大两倍。
- 保留列表中的奇数。
- 求列表中所有元素的和。
为了使用高阶函数, 有时创建一次性的小型函数更便利。 这便是匿名函数存在的原因。
lambda 关键字在 Python 表达式内创建匿名函数。
然而, Python 简单的句法限制了 lambda 函数的定义体只能使用纯表达式。 换句话
说, lambda 函数的定义体中不能赋值, 也不能使用 while 和 try 等 Python 语句。
如果使用 lambda 表达式导致一段代码难以理解,可以这样重构。
(1) 编写注释, 说明 lambda 表达式的作用。
(2) 研究一会儿注释, 并找出一个名称来概括注释。
(3) 把 lambda 表达式转换成 def 语句, 使用那个名称来定义函数。
(4) 删除注释。
可调用对象:
除了用户定义的函数, 调用运算符(即 ()) 还可以应用到其他对象上。 如果想判断对象能否调用, 可以使用内置的 callable() 函数。
Python 数据模型文档列出了 7 种可调用对象,
- 用户定义的函数
使用 def 语句或 lambda 表达式创建。 - 内置函数
使用 C 语言(CPython) 实现的函数, 如 len 或 time.strftime。 - 内置方法
使用 C 语言实现的方法, 如 dict.get。 - 方法
在类的定义体中定义的函数。 - 类
调用类时会运行类的 new 方法创建一个实例, 然后运行 init 方法, 初始化实例, 最后把实例返回给调用方。 因为 Python 没有 new 运算符, 所以调用类相当于调用函数。 - 类的实例
如果类定义了 call 方法, 那么它的实例可以作为函数调用。 - 生成器函数
使用 yield 关键字的函数或方法。 调用生成器函数返回的是生成器对象。生成器函数在很多方面与其他可调用对象不同,后面会细讲,生成器函数还可以作为协程,后面的课程会细讲。
Python 中有各种各样可调用的类型, 因此判断对象能否调用, 最安全的方法是使用内
置的 callable() 函数:
print([callable(obj) for obj in [int, 'str', str]])
如何让用户自定义一个可调用类型呢?
不仅 Python 函数是真正的对象, 任何 Python 对象都可以表现得像函数。 为此, 只需实现实例方法 call。
import random
class BingoCage:
def __init__(self, items):
self._items = list(items)
random.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
return self.pick()
bingo = BingoCage(range(3))
print(bingo.pick())
print(bingo())
print(callable(bingo))
实现 call 方法的类是创建函数类对象的简便方式, 此时必须在内部维护一个状态,让它在调用之间可用, 例如 BingoCage 中的剩余元素。
装饰器就是这样。 装饰器必须是函数, 而且有时要在多次调用之间“记住”某些事 [ 例如备忘(memoization) , 即缓存消耗大的计算结果, 供后面使用 ]
创建保有内部状态的函数, 还有一种截然不同的方式——使用闭包。 闭包和装饰器会在后面讨论。 下一节课从 函数内省 开始讲解,说说有关函数属性的知识。