1. 一等函数
Python 中,函数是一等对象。编程语言理论家把“一等对象”定义为满足下述条件的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
在 Python 中,整数、字符串和字典都是一等对象。函数也可以作为一等对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def factorial(n): """ 阶乘 :param n: num :return: num! """ return 1 if n < 1 else n * factorial(n-1) factorial.__doc__ Out[2]: '\n 阶乘\n :param n: num \n :return: num!\n ' factorial(30) Out[3]: 265252859812191058636308480000000 type(factorial) Out[4]: function
|
这是一个控制台会话,def factorial(n):是在“运行时”创建一个函数。__doc__ 是函数对象众多属性中的一个。 factorial 是 function 类的实例。
函数对象的“一等”本性:
我们可以把 factorial 函数赋值给变量 fact,然后通过变量名调用。我们还能把它作为参数传给 map 函数。map 函数返回一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象,这里 是 range(11))中各个元素上得到的结果。
1 2 3 4 5 6 7 8 9 10 11
| fact = factorial fact(5) Out[5]: 120 type(fact) Out[6]: function map(fact, range(5)) Out[7]: <map at 0x212ba2663c8> list(map(fact, range(5))) Out[8]: [1, 1, 2, 6, 24]
|
有了一等函数,就可以使用函数式风格编程。函数式编程的特点之一是使用高阶函数。
2. 高阶函数
接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-order function)。在函数式编程范式中,最为人熟知的高阶函数有 map、filter、reduce。
此外,内置函数 sorted 也是:可选的 key 参数用于提供一个函数,它会应用到各个元素上进行排序。
1 2 3 4 5
| def reverse(word): return word[::-1] fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] sorted(fruits, key=reverse) Out[16]: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
|
map、filter和reduce的现代替代品:
map 和 filter 还是内置函数,但是由于引入了列表推导和生成器表达式,它们变得没那么重要了。列表推导或生成器表达式具有 map 和 filter 两个函数的功能,而且更易于阅读。
1 2 3 4 5 6 7 8 9 10
| list(map(fact,range(6))) Out[17]: [1, 1, 2, 6, 24, 120] [fact(n) for n in range(6)] Out[18]: [1, 1, 2, 6, 24, 120] list(map(fact, filter(lambda n:n%2, range(6)))) Out[19]: [1, 6, 120] [fact(n) for n in range(6) if n%2] Out[20]: [1, 6, 120]
|
1 2 3 4 5 6 7
|
from functools import reduce from operator import add reduce(add, range(6)) Out[25]: 15
|
sum 和 reduce 的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值归约成一个值。all 和 any 也是内置的归约函数。
all(iterable): 如果 iterable 的每个元素都是真值,返回 True;all([]) 返回 True。 有0即为假
any(iterable) : 只要 iterable 中有元素是真值,就返回 True;any([]) 返回 False。 有1即为真
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32 Type "copyright", "credits" or "license()" for more information. >>> all([1,0]) False >>> all([1]) True >>> all([]) True >>> any([1,0]) True >>> any([0]) False >>> any([]) False
|
3. 可调用对象
除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的 callable() 函数。Python 数据模型文档列出了 7 种可调用对象。
用户定义的函数
使用 def 语句或 lambda 表达式创建。
内置函数
使用 C 语言(CPython)实现的函数,如 len 或 time.strftime。
内置方法
使用 C 语言实现的方法,如 dict.get。
方法
在类的定义体中定义的函数。
类
调用类时会运行类的 __new__ 方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。因为 Python 没有 new 运算符,所以调用类相当于调用函数。(通常,调用类会创建那个类的实例,不过覆盖 __new__ 方法的话,也可能出现 其他行为。)
类的实例
如果类定义了__call__方法,那么它的实例可以作为函数调用。
生成器函数
使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。
Python 中有各种各样可调用的类型,因此判断对象能否调用,最安全的方法是使用内置的 callable() 函数:
1 2
| >>> [callable(item) for item in [str, abs, 100]] [True, True, False]
|
4. 用户可调用的类型
不仅 Python 函数是真正的对象,任何 Python 对象都可以表现得像函数。为此,只需实现实例方法 __call__。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import random
class BingoCase(object): def __init__(self, items): self._items = list(items) random.shuffle(self._items)
def pickitem(self): try: return self._items.pop() except IndexError as e: raise LookupError('pick from empty BingoCase')
def __call__(self, *args, **kwargs): return self.pickitem()
bingo = BingoCase([1, 2, 3, 3, 4, 5]) print(bingo.pickitem()) print(bingo()) print(callable(bingo))
|
实现__call__方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用,例如 BingoCage 中的剩余元素。装饰器就是这样,有时要在多次调用之间“记住”某些事(例如备忘memoization),即缓存消耗大的计算结果,供后面使用。
创建保有内部状态的函数,还有一种截然不同的方式——闭包。
5. 函数注解
Python3提供了一种句法,用于为函数声明中的参数和返回值附加元数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| def clip(text: str, max_len: 'int > 0' = 8) -> str: """ 在max_len前面或后面的第一个空格处截断文本 """ end = None space_before = space_after = '' if len(text) > max_len: space_before = text.rfind(' ', 0, max_len) print(space_before) if space_before >= 0: end = space_before else: space_after = text.rfind(' ', max_len) print(space_after) if space_after >= 0: end = space_after if end is None: end = len(text) return text[:end].rstrip()
print(clip("1adsfd2sdfjkl 3dsfa 4jskldf")) print(clip.__annotations__) ''' 1adsfd2sdfjkl 3dsfa {'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>} '''
|
函数声明中的各个参数可以在 : 之后增加注解表达式。如果参数有默认值,注解放在参数名和 = 号之间。如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达式。那个表达式可以是任何类型。注解中最常用的类型是类(如 str 或 int)和字符串(如’int > 0’)。在示例中,max_len 参数的注解用的是字符串。
注解不会做任何处理,只是存储在函数的 __annotations__ 属性(一个字典)中。
Python 对注解所做的唯一的事情是,把它们存储在函数的 __annotations__ 属性里。仅此而已,Python 不做检查、不做强制、不做验证,什么操作都不做。换句话说,注解对Python 解释器没有任何意义。注解只是元数据,可以供 IDE、框架和装饰器等工具使用。
6. 支持函数式编程的包
operator模块
operator 模块为多个算术运算符提供了对应的函数,从而避免编写 lambda a, b: a*b 这种平凡的匿名函数。
1 2 3 4 5 6 7 8 9 10 11
| from functools import reduce def func(n): return reduce(lambda a, b:a*b, range(1,n+1)) func(4) Out[2]: 24 from operator import mul def fact(n): return reduce(mul, range(1,n+1)) fact(4) Out[3]: 24
|
operator 模块中还有一类函数,能替代从序列中取出元素或读取对象属性的 lambda 表达式:因此,itemgetter 和 attrgetter 其实会自行构建函数。
itemgetter 使用 [] 运算符,因此它不仅支持序列,还支持映射和任何实现 __getitem__ 方法的类。itemgetter 的常见用途:根据元组的某个字段给元组列表排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| metro_data = [ ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ]
from operator import itemgetter for city in sorted(metro_data, key=itemgetter(1)): print(city)
names = itemgetter(0, 1) for city in metro_data: print(names(city)) """ ('Tokyo', 'JP') ('Delhi NCR', 'IN') ('Mexico City', 'MX') ('New York-Newark', 'US') ('Sao Paulo', 'BR') """
|
attrgetter 与 itemgetter 作用类似,它创建的函数根据名称提取对象的属性。如果把多个属性名传给 attrgetter,它也会返回提取的值构成的元组。此外,如果参数名中包含 .(点号),attrgetter 会深入嵌套对象,获取指定的属性。这些行为如下例所示,这个控制台会话不短,因为我们要构建一个嵌套结构,这样才能展示attrgetter 如何处理包含点号的属性名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| from collections import namedtuple
metro_data = [ ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)), ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)), ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)), ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)), ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)), ]
LatLong = namedtuple('LatLong', 'lat long') Metropolis = namedtuple('Metropolis', 'name cc pop coord') metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long)) for name, cc, pop, (lat, long) in metro_data]
metro_areas[0] Out[3]: Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722, long=139.691667)) metro_areas[0].coord.lat Out[4]: 35.689722 from operator import attrgetter name_lat = attrgetter('name', 'coord.lat') for city in sorted(metro_areas, key=attrgetter('coord.lat')): print(name_lat(city)) ('Sao Paulo', -23.547778) ('Mexico City', 19.433333) ('Delhi NCR', 28.613889) ('Tokyo', 35.689722) ('New York-Newark', 40.808611)
|
operator 模块中定义的部分函数(省略了以 _ 开头的名称,因为它们基本上是实现细节):
1 2
| [name for name in dir(operator) if not name.startswith('_')] ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor']
|
methodcaller 它的作用与 attrgetter和 itemgetter 类似,它会自行创建函数。methodcaller 创建的函数会在对象上调用参数指定的方法。
1 2 3 4 5 6 7 8 9 10
| from operator import methodcaller
s = "abcd efg" upcase = methodcaller('upper') upcase(s) Out[16]: 'ABCD EFG' replace_case = methodcaller('replace',' ','-') replace_case(s) Out[18]: 'abcd-efg'
|