fluent python- 第 5 章 一等函数-面向对象(附
第 4 章 文本和字节序列
人类使用文本, 计算机使用字节序列。
第 5 章 一等函数
前言:
在 Python 中, 函数是一等对象。 编程语言理论家把“一等对象”定义为满足下述条件的程序实体:
1、在运行时创建
2、能赋值给变量或数据结构中的元素
3、能作为参数传给函数
4、能作为函数的返回结果
5.1 把函数视作对象
map、 filter和reduce的现代替代品
函数式语言通常会提供 map、 filter 和 reduce 三个高阶函数( 有时使用不同的名称) 。 在 Python 3 中, map 和 filter 还是内置函数, 但是由于引入了列表推导和生成器表达式, 它们变得没那么重要了。 列表推导或生成器表达式具有 map 和 filter 两个函数的功能, 而且更易于阅读, 如示例:
>>> list(map(fact, range(6))) ➊ # fact是一个提前定义好的阶乘函数n!
[1, 1, 2, 6, 24, 120]
>>> [fact(n) for n in range(6)] ➋
[1, 1, 2, 6, 24, 120]
>>> list(map(factorial, filter(lambda n: n % 2, range(6)))) ➌
[1, 6, 120]
>>> [factorial(n) for n in range(6) if n % 2] ➍
[1, 6, 120]
>>>
5.3 匿名函数
Python 简单的句法限制了 lambda 函数的定义体只能使用纯表达
式。 换句话说, lambda 函数的定义体中不能赋值, 也不能使用 while
和 try 等 Python 语句,在参数列表中最适合使用匿名函数。
>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=lambda word: word[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
除了作为参数传给高阶函数之外, Python 很少使用匿名函数。 由于句法上的限制, 非平凡的 lambda 表达式要么难以阅读, 要么无法写出.
5.4 可调用对象
除了用户定义的函数, 调用运算符( 即 ()) 还可以应用到其他对象
上。 如果想判断对象能否调用, 可以使用内置的 callable()
函数。
Python 数据模型文档列出了 7 种可调用对象:
- 用户定义的函数:使用 def 语句或 lambda 表达式创建。
- 内置函数
- 内置方法
- 方法
- 类
- 类的实例
- 生成器函数
5.5 用户定义的可调用类型
不仅 Python 函数是真正的对象, 任何 Python 对象都可以表现得像函
数。 为此, 只需实现实例方法 __call__
。
import random
class BingoCage:
def __init__(self, items):
self._items = list(items)
random.shuffle(self._items) # 随机洗牌
def pick(self):
try: # list = [1,2,3]; list.pop()结果为3,再输入list,结果为[1,2]
return self._items.pop()
except ImportError:
raise LookupError('pick from empty BigoCate')
def __call__(self):
return self.pick() # 实现bingo.pick() 的快捷方式是 bingo()
bingo = BingoCage(range(3))
print( bingo.pick() ) # 结果:0 or 1 or 2
print( bingo() ) # 上方已经定义了随机洗牌 ; ⚠️bingo已经是实例,我们还可以 实例(),表现的像函数
print( callable(bingo) ) # True
5.6 函数内省
除了__doc__
, 函数对象还有很多属性。 使用 dir 函数可以探知
factorial
(已定义的一个函数) 具有下述属性:
>>> dir(factorial)
['__annotations__', '__call__', '__class__', '__closure__', '__code__',
'__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
'__format__', '__ge__', '__get__', '__getattribute__', '__globals__',
'__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__',
'__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__']
>>>
列出常规对象没有而函数有的属性
>>> class C: pass # ➊
>>> obj = C() # ➋
>>> def func(): pass # ➌
>>> sorted(set(dir(func)) - set(dir(obj))) # ➍
['__annotations__', '__call__', '__closure__', '__code__', '__defaults__',
'__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']
>>>
➊ 创建一个空的用户定义的类。
➋ 创建一个实例。
➌ 创建一个空函数。
➍ 计算差集, 然后排序, 得到类的实例没有而函数有的属性列表。
5.7 从定位参数到仅限关键字参数
*args
参数会被存入一个元组
**kwargs
参数会被存入一个字典
5.11 本章小结
本章的目标是探讨 Python 函数的一等本性。 这意味着, 我们可以把函数赋值给变量、 传给其他函数、 存储在数据结构中, 以及访问函数的属性, 供框架和一些工具使用。 高阶函数是函数式编程的重要组成部分,即使现在不像以前那样经常使用 map、 filter 和 reduce 函数了, 但是还有列表推导( 以及类似的结构, 如生成器表达式) 以及 sum、 all和 any 等内置的归约函数。 Python 中常用的高阶函数有内置函数sorted
、 min
、 max
和 functools. partial
。
Python 有 7 种可调用对象, 从 lambda 表达式创建的简单函数到实现__call__
方法的类实例。 这些可调用对象都能通过内置的callable()
函数检测。 每一种可调用对象都支持使用相同的丰富句法声明形式参数, 包括仅限关键字参数和注解——二者都是 Python 3 引入的新特性。
Python 函数及其注解有丰富的属性, 在 inspect
模块的帮助下, 可以读取它们。 例如, Signature.bind
方法使用灵活的规则把实参绑定到形参上, 这与 Python 使用的规则一样。
最后, 本章介绍了 operator
模块中的一些函数, 以及
functools.partial
函数, 有了这些函数, 函数式编程就不太需要功
能有限的 lambda
表达式了。
杂谈
Python 是函数式语言吗?
编程语言“范式”已近末日, 它们是旧时代的遗留物, 令人厌烦。 既然现代语言的设计者对范式不屑一顾, 那么我们的课程为什么要像奴隶一样对其言听计从?
在那篇论文中, 下面这一段点名提到了 Python:
对 Python、 Ruby 或 Perl 这些语言还要了解什么呢? 它们的设
计者没有耐心去精确实现林奈层次结构; 设计者按照自己的意
愿从别处借鉴特性, 创建出完全无视过往概念的大杂烩。
Krishnamurthi 指出, 不要试图把语言归为某一类; 相反, 把它们视
作特性的聚合更有用。
为 Python 提供一等函数打开了函数式编程的大门, 不过这并不是
Guido 的目的。 他在“Origins of Python's Functional Features”一文
( http://python-history.blogspot.com/2009/04/origins-of-pythonsfunctional-features.html) 中说, map、 filter 和 reduce 的最初目的是为 Python 增加 lambda 表达式。 这些特性都由 Amrit Prem 贡献, 添加在 1994 年发布的 Python 1.0 中( 参见 CPython 源码中的
Misc/HISTORY 文件,https://hg.python.org/cpython/file/default/Misc/HISTORY) 。