一等函数

“一等对象”定义为满足下述条件的程序实体:
在运行时创建
能赋值给变量或数据结构中的元素
能作为参数传给函数
能作为函数的返回结果

sum 和 reduce 的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把
一系列值归约成一个值。
all 和 any 也是内置的归约函数。
all(iterable)
如果 iterable 的每个元素都是真值,返回 True;all([]) 返回 True。
any(iterable)
  只要 iterable 中有元素是真值,就返回 True;any([]) 返回 False。

匿名函数
lambda 关键字在 Python 表达式内创建匿名函数

与 def 语句一样,lambda 表达式会创建函数对象。这是
Python 中几种可调用对象的一种。

调用运算符(即 ())可调用对象:

·用户定义的函数
·内置函数
  使用 C 语言(CPython)实现的函数,如 len 或 time.strftime。
·内置方法
  使用 C 语言实现的方法,如 dict.get。
·类
  调用类时会运行类的 new 方法创建一个实例,然后运行 init 方法,初始
化实例,最后把实例返回给调用方。调用类相当于调用函数。
·类的实例
  如果类定义了 call 方法,那么它的实例可以作为函数调用。
·生成器函数(比较特殊,详见 14 章。生成器函数还可以作为协程,参见第 16 章。)
  使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。

callable() 函数 判断对象能否调用

不仅 Python 函数是真正的对象,任何 Python 对象都可以表现得像函数。
为此,只需实现实例方法 call

实现 call 方法的类是创建函数类对象的简便方式

除了 doc,函数对象还有很多属性。使用 dir 函数可以探知 一个对象
具有的属性
例如

1
2
>>> dir(int)
>>> ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

Python 3 提供了仅限关键字参数(keyword-only argument)
调用函数时使用 和* “展开” 可迭代对象,映射到单个参数。

*args
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装
为一个tuple。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
#笨方法
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
>>> 14

#聪明方法,可以在list或tuple前面加一个*号,把list或tuple的元素变成
#可变参数传进去
>>> nums = [1, 2, 3]
>>> calc(*nums)
>>> 14

**kwargs
关键字参数允许你传入0个或任意个含参数名的参数,这些
关键字参数在函数内部自动组装为一个dict

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

#笨方法
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
>>> name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

#聪明方法
#用**extra表示把extra这个dict的所有key-value用关键字参数
#传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的
#一份拷贝,对kw的改动不会影响到函数外的extra
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
>>> name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

仅限关键词参数 KEYWORD_ONLY

和关键字参数kw不同,仅限关键词参数需要一个特殊分隔符, 后面的参数被视为仅限关键词参数。

用法:

1
2
3
4
5
def person(name, age, *, city, job):
print(name, age, city, job)

>>> person('Jack', 24, city='Beijing', job='Engineer')
>>> Jack 24 Beijing Engineer

如果函数定义中已经有了一个可变参数,后面跟着的仅限关键词参数就
不再需要一个特殊分隔符*了:

1
2
def person(name, age, *args, city, job):
print(name, age, args, city, job)

仅限关键词参数必须传入参数名,这和位置参数不同。如果没有传入参数名,
调用将报错:

1
2
3
4
5
6
>>> person('Jack', 24, 'Beijing', 'Engineer')
>>> Traceback (most recent call last):
>>> File "<stdin>", line 1, in <module>
>>> TypeError: person() takes 2 positional arguments but 4 were given
>>> 由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,
>>> 但person()函数仅接受2个位置参数。

使用仅限关键词参数时,要特别注意,如果没有可变参数,就必须加一个作为特殊分隔符。
如果缺少,Python解释器将无法识别位置参数和仅限关键词参数:

仅限关键词参数不一定要有默认值,但此时强制必须传入实参。

Python 中函数的参数可以分为两大类:

定位参数(Positional):表示参数的位置是固定的。
比如对于函数 foo(a, b) 来说,foo(1, 2) 和 foo(2, 1) 就是截然不同的,
a 和 b 的位置是固定的,不可随意调换。
关键词参数(Keyword):表示参数的位置不重要,但是参数名称很重要。
比如 foo(a = 1, b = 2) 和 foo(b = 2, a = 1) 的含义相同。

def foo(args, n=1, **kwargs):
print(n)
这个函数在调用时,如果参数 n 不指定名字,就会被前面的
args 处理掉,
如果指定的名字不是 n,又会被后面的 **kwargs 处理掉,
所以参数 n 必须精确的以 (n = xxx) 的形式出现,也就是 KEYWORD_ONLY。

函数对象有个 defaults 属性,它的值是一个元组,里面保存着定位参数和关键字
参数的默认值。仅限关键字参数的默认值在 kwdefaults 属性中。然而,参数的名
称在 code 属性中,它的值是一个 code 对象引用,自身也有很多属性。

text 定位参数 max_len 关键词参数 a 仅限关键词参数 如果a没有默认值,clip初始化时需要传入a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def clip(text, max_len=80,*,a):
"""在max_len前面或后面的第一个空格处截断文本
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None: # 没找到空格
end = len(text)
return text[:end].rstrip()

>>> clip.__defaults__
>>> (80,)
>>> clip.__code__ # doctest: +ELLIPSIS
>>> <code object clip at 0x...>
>>> clip.__code__.co_varnames
>>> ('text', 'max_len', 'end', 'space_before', 'space_after')
>>> clip.__code__.co_argcount
>>> 2

提取函数的签名
inspect.signature 函数返回一个 inspect.Signature 对象,它有一
个 parameters 属性,这是一个有序映射,把参数名和 inspect.Parameter 对象对应起
来。各个 Parameter 属性也有自己的属性,例如 name、default 和 kind。特殊的
inspect._empty 值表示没有默认值,注意 None 是有效的默认值不会显示inspect._empty(也经常这么做),
而且这么做是合理的。

1
2
3
4
5
6
7
8
9
10
11
>>> from inspect import signature
>>> sig = signature(clip)
>>> sig # doctest: +ELLIPSIS
>>> <inspect.Signature object at 0x...>
>>> str(sig)
>>> '(text, max_len=80)'
>>> for name, param in sig.parameters.items():
>>> print(param.kind, ':', param.name, '=', param.default)

POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80

注解
inspect.Parameter 对象还有一个 annotation(注解)属性

函数声明中的各个参数可以在 : 之后增加注解表达式。如果参数有默认值,注解放在参
数名和 = 号之间。如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达
式。那个表达式可以是任何类型。注解中最常用的类型是类(如 str 或 int)和字符串
(如 ‘int > 0’)。在示例 5-19 中,max_len 参数的注解用的是字符串。

1
2
3
4
5
def clip(text:str, max_len:'int > 0'=80) -> str: 
return text

clip.__annotations__
{'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}

Python 对注解所做的唯一的事情是,把它们存储在函数的 annotations 属性里。仅
此而已,Python 不做检查、不做强制、不做验证,什么操作都不做。

1
2
3
4
5
6
7
8
9
>>> from inspect import signature
>>> sig = signature(clip)
>>> sig.return_annotation
>>> <class 'str'>
>>> for param in sig.parameters.values():
>>> ... note = repr(param.annotation).ljust(13)
>>> ... print(note, ':', param.name, '=', param.default)
>>> <class 'str'> : text = <class 'inspect._empty'>
>>> 'int > 0' : max_len = 80

operator 模块

使用 reduce 函数和一个匿名函数计算阶乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import reduce
def fact(n):
return reduce(lambda a, b: a*b, range(1, n+1))

使用 reduce 和 operator.mul 函数计算阶乘
from functools import reduce
from operator import mul
def fact(n):
return reduce(mul, range(1, n+1))


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)),
... ]

itemgetter(1) 的作用与 lambda fields: fields[1] 一样:创建一个接受集合
的函数,返回索引位 1 上的元素。

1
2
3
4
5
6
7
8
9
from operator import itemgetter
for city in sorted(metro_data, key=itemgetter(1)):
... print(city)
...
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833))
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
('New York-Newark', 'US', 20.104, (40.808611, -74.020386))

如果把多个参数传给 itemgetter,它构建的函数会返回提取的值构成的元组

1
2
3
4
5
6
7
8
9
>>> cc_name = itemgetter(1, 0)
>>> for city in metro_data:
>>> ... print(cc_name(city))
>>> ...
>>> ('JP', 'Tokyo')
>>> ('IN', 'Delhi NCR')
>>> ('MX', 'Mexico City')
>>> ('US', 'New York-Newark')
>>> ('BR', 'Sao Paulo')