fluent python (五)一等函数

fluent python (五)一等函数

“一等对象”的定义:为满足下列条件的程序实体

  1. 在运行时创建
  2. 能赋值给变量或数据结构中的元素
  3. 能作为参数穿给函数
  4. 能作为函数的返回结果

在python中,整数、字符串、字典都是一等对象。人们常把“把函数视为一等对象”称作为“一等函数”。在python中,所有函数都是一等函数。

1
2
3
4
5
6
7
def factorial(n):
"""return n!"""
return 1 if n < 2 else n * factorial(n - 1)

print(factorial(12))
print(factorial.__doc__)
print(type(factorial))

我们利用递归实现了一个math库中的一个函数factorial也就是阶乘函数。__doc__属性是生成对象的帮助文档。

1
2
3
4
5
fact = factorial
print(fact)
print(fact(10))
print(map(fact, range(11)))
print(list(map(fact, range(10))))

我们把函数factorial作为一个变量传给fact,fact具有和factorial一样的功能,我们把这个函数作为参数传给map,并可以得到一个可迭代的对象。

高阶函数

接受函数为参数,或者把函数作为返回结果的函数就是高阶函数,比如map, sorted.在python函数式编程中为人熟知的高阶函数有map,filter,reduce和apply,其中apply已经被淘汰了。如果想使用不定量的参数调用函数,可以直接如fn(*args, **keywords),这样使用

在python3中,map和filter还是内置函数,但是由于引入了列表推导和生成器表达式,他们没这么重要了。

1
2
3
4
5
print(list(map(fact, range(6))))
print([fact(n) for n in range(6)])

print(list(map(fact, filter(lambda n: n % 2, range(6)))))
print([fact(n) for n in range(6) if n % 2 == 1])

可以发现列表推导的反而更容易书写和阅读。

在python3中,map和filter返回生成器(一种迭代器),因此他们直接替代品是生成器表达式。

在python2中,reduce是内置函数,python3中放置到了functools模块了,这个函数最常用于求和。

1
2
3
4
5
6
from functools import reduce
from operator import add
from operator import sub
print(reduce(add, range(100)))
print(reduce(sub, range(100)))
print(sum(range(100)))

如果仅仅只是用于求和的话,我们可以用内置函数sum来实现。

除此之外还有两个常用的归约函数:all(iterable)和any(iterable), 从字面意思上就知道前者需要序列中所有元素都为真才返回真,而后者只需存在。

匿名函数

反转拼写给单词倒序排序。

1
2
words = ['az', 'by', 'cx']
print(sorted(words, key=lambda word: word[::-1], reverse=True))

lambda除了作为参数传给高阶函数一般难以书写或者较难阅读,更多的只是一个语法糖,实质上与def一样,会创建一个函数对象,也是python中几种可调用对象的一种。

可调用对象

除了用户定义的函数,调用预算符( 即() )还可以应用到其他对象上,如果想判断对象能否调用,可以使用callable()函数。

python数据模型文档给出的7种可调用对象:

  1. 用户定义的函数:用def,lambda 表达式创建的
  2. 内置函数: 使用C语言(Cpython)实现的函数,如len,time.strftime;
  3. 内置方法: C语言实现,如dict.get
  4. 方法: 在类的定义体中定义的函数
  5. 类:调用类方法时会创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。因为Python没有new运算符,所以调用类相当于调用函数。
  6. 类的实例:如果类定义__call__方法,那么他的实例是可以作为函数调用。
  7. 生成器函数:使用yield关键字的函数或者方法,返回的是生成器对象。

用户定义的可调用类型

任何python对象的都可以表现的像函数,只要实现了__call__实例方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from random import shuffle

class Huchi:
def __init__(self, items):
self._items = list(items)
shuffle(self._items) #打乱

def pick(self):
try:
return self._items.pop()
except IndexError:
print('Huchi is hungry')

def __call__(self):
return self.pick()

if __name__ == "__main__":
littleHuchi = Huchi(range(3))

for i in range(4):
print(littleHuchi())

print(callable(littleHuchi))

函数内省

使用dir可以探知对象所具有的属性。大多数属性是python对象所共有的。

1
2
3
4
class C: pass
obj = C()
def func(): pass
print(sorted(set(dir(func)) - set(dir(obj))))

我们可以看到函数独有而一般类对象所没有的属性。

名称类型说明
__annotations__dict参数和返回值的注解
__call__method-wrapper实现()运算符,即可调用对象协议
__closure__tuple函数闭包,自由变量的绑定(通常是None)
__code__code编译成字节码的函数元数据和函数定义体
__defaults__tuple形式参数的默认值
__get__method-wrapper实现只读描述符协议
__globals__dict函数所在模块中的全局变量
__kwdefaults__dict仅限关键字形式参数的默认值
__name__str函数名称
__qualname__str函数的限定名称

仅限关键字参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def tag(name, *content, cls=None, **attrs):
"""return one or more html tag(s)"""
if cls is not None:
attrs['class'] = cls

if attrs:
attr_str = ''.join(' %s="%s"' % (attr, value)
for attr, value in
sorted(attrs.items()))
else :
attr_str = ''
if content:
return '\n'.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content)
else : return '<%s%s />' % (name, attr_str)

print(tag('br'))
print(tag('p', 'hello', 'huchi', cls='huchi', id=33))

定义函数时若想指定仅限关键字参数,要把这些放在*后

1
2
def f(a, *, b):
return a, b

获取关于参数的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def clip(text, max_len=80):
"""
return the string before max_len or after it
"""
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()

同文件下,我们可以用交互式的解释器来测试一下:

1
2
3
4
5
6
7
8
9
>>> from test import clip
>>> clip.__defaults__
(80,)
>>> clip.__code__
<code object clip at 0x01563020...>
>>> clip.__code__.co_varnames
('text', 'max_len', 'end', 'space_before', 'space_after')
>>> clip.__code__.co_argcount
2

在inspect模块中有更好的方式:

1
2
3
4
5
6
7
8
9
10
11
12
>>> from inspect import signature
>>> sig = signature(clip)
>>> sig
<Signature (text, max_len=80)>
>>> str(sig)
'(text, max_len=80)'
>>> for name, param in sig.parameters.items():
... print(param.kind, ':', name, '=', param.default)
...
...
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80

显然inspect._empty表示没有默认值。kind属性的值是_ParameterKind类中的5个值之一:1.POSITIONAL_OR_KEYWORD定位参数和关键字参数传入的形参,2.VAR_POSITIONAL定位参数元组,3.VAR_POSITIONAL关键字参数字典4.KEYWORD_ONLY仅限关键字参数5.POSITIONAL_ONLY仅限定位参数(python声明函数暂时不支持,C实现的且不接受关键字参数的函数(如divmod)支持)

inspect.Signature对象还有bind方法,可以把任意参数绑定到签名的形参上,所用规则与实参到形参的匹配方式相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> import inspect
>>> from test import tag
>>> sig = inspect.signature(tag)
>>> huchi_tag = {'name': 'img', 'title': 'huchi"s photo', 'src': 'huchi.jpg', 'cls': 'framed'}
>>> bound_args = sig.bind(**huchi_tag)
>>> bound_args
<BoundArguments (name='img', cls='framed', attrs={'src': 'huchi.jpg', 'title': 'huchi"s photo'})>
>>> for name, value in bound_args.arguments.items():
... print(name, '=', value)
...
name = img
cls = framed
attrs = {'src': 'huchi.jpg', 'title': 'huchi"s photo'}
>>> del huchi_tag['name'] #此时再调用inspect.bind()就会报错
>>> bound_args = sig.bind(**huchi_tag)
Traceback (most recent call last):...

函数注解

1
def clip(text:str, max_len:'int > 0'=80) -> str: pass

如此添加函数的注解。

对于注解不会做任何处理,只会存储在函数__annotations__(dict)属性中

1
2
3
>>> from test import clip
>>> clip.__annotations__
{'text': <class 'str'>, 'return': <class 'str'>, 'max_len': 'int > 0'}

这些东西不能派到什么实际的用场,在inspect.signature()函数中可以提取

1
2
3
4
5
6
>>> 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

1
2
>>> def fact(n):
... return reduce(mul, range(1, n+1))

优美的给元组列表排序

1
2
3
from operator import itemgetter
a = [('huchi1', 2), ('huchi2', 3), ('huchi3', 1)]
a.sort(key = itemgetter(1))
1
2
for it in a:
print(itemgetter(1, 0)(it))

提取元组中的元素重新生成为一个元组。我们可以f = itemgetter(1, 0)然后再调用f函数。

其实itemgetter(1)的作用与lambda fields: fields[1]一样。

itemgetter使用[]运算符,因此他支持序列也支持映射和任何实现__getitem__方法的类。

attrgetter与itemgetter作用类似,创建的函数根据名称提取对象的属性。 比如key是元祖列表中的元祖中的元祖的元素就可以使用attrgetter.

functools.partial冻结参数

1
2
3
4
5
6
7
>>> from operator import mul
>>> from functools import partial
>>> triple = partial(mul, 3)
>>> triple(4)
12
>>> [triple(i) for i in range(1, 10)]
[3, 6, 9, 12, 15, 18, 21, 24, 27]
文章目录
  1. 1. fluent python (五)一等函数
    1. 1.1. 高阶函数
    2. 1.2. 匿名函数
    3. 1.3. 可调用对象
    4. 1.4. 用户定义的可调用类型
    5. 1.5. 函数内省
    6. 1.6. 仅限关键字参数
    7. 1.7. 获取关于参数的信息
    8. 1.8. 函数注解
    9. 1.9. 支持函数式编程的包
      1. 1.9.1. operator
      2. 1.9.2. functools.partial冻结参数
|