Python系列之functools-控制函数的工具

这篇文章学习了Python中功能强大的模块functools,这里主要学习了可以固定函数(方法)的某些参数的函数并返回函数的partialpartialmethod,可以用于消除装饰器副作用的装饰器函数wrapsupdate_wrapper,可以进行化简运算的函数reduce,可以将比较函数转化为键函数的cmp_to_key

简介

functoolsPython 中很简单但也很重要的模块,主要是一些 Python 高阶函数相关的函数。 关于高阶函数,这是函数式编程范式中很重要的一个概念,简单地说, 就是一个可以接受函数作为参数或者以函数作为返回值的函数,因为 Python 中函数是一类对象, 因此很容易支持这样的函数式特性。

functools中比较常用的函数如下:

  • partial:固定函数的某些参数,返回一个新的函数
  • partialmethod:针对方法的partial函数
  • wraps:消除装饰器函数带来的副作用
  • update_wrapper:功能更加强大的wraps
  • reduce:化简函数
  • cmp_to_key:将比较函数转化为键函数

functools中所有的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import functools
functools.__all__
['update_wrapper',
'wraps',
'WRAPPER_ASSIGNMENTS',
'WRAPPER_UPDATES',
'total_ordering',
'cmp_to_key',
'lru_cache',
'reduce',
'partial',
'partialmethod',
'singledispatch']


偏函数相关

partial

partial的作用:把一个函数的某些参数给固定住(也就是设置默认值)返回一个新的函数,调用这个新函数会更简单。接下来将使用示例进行学习。

假设有如下函数:

1
2
def multiply(x, y):
return x * y

现在,我们想返回某个数的2倍,即:

1
2
3
4
5
6
# 3的两倍
multiply(3, y=2)
6
# 4的两倍
multiply(4, y=2)
8

上面的调用有点繁琐,每次都要传入 y=2,我们想到可以定义一个新的函数,y=2 作为默认值,即:

1
2
def double(x, y=2):
return multiply(x, y)

现在,我们可以这样调用了:

1
2
3
4
5
# 使用带默认值的函数计算
double(3)
6
double(4)
8

上面设置默认值的方法虽然可以达到目的,但是需要重新定义一个函数,同时如果y值发生了变化还需要每次修改,比较麻烦,这里可以使用partial达到类似的目的,并且不用重新定义函数:

1
2
3
4
5
6
7
8
from functools import partial

# multiply函数保持不变,设置y的默认值
double = partial(multiply, y=2)

# 调用
double(3)
6

partial 接收函数 multiply 作为参数,固定 multiply 的参数 y=2,并返回一个新的函数给 double,这跟我们自己定义 double 函数的效果是一样的。

所以,简单而言,partial 函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数

需要注意的是:使用partial函数来固定某些参数的时候最好是指定固定哪个参数的值,如partial(multiply, y=2),而不是直接partial(multiply, 2),因为默认会从参数的最左边来设置,那么partial(multiply, 2)就等同于partial(multiply, x=2)


partialmethod

partialmethod的作用基本类似于 partial, 不过仅作用于方法。举个例子就很容易明白:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cell(object):
def __init__(self):
self._alive = False
@property
def alive(self):
return self._alive
def set_state(self, state):
self._alive = bool(state)
set_alive = partialmethod(set_state, True)
set_dead = partialmethod(set_state, False)

c = Cell()
c.alive
False
c.set_alive()
c.alive
True


装饰器相关

说到接受函数为参数,以函数为返回值,在 Python 中最常用的当属装饰器了。 functools 库中装饰器相关的函数是 update_wrapperwraps,还搭配 WRAPPER_ASSIGNMENTSWRAPPER_UPDATES 两个常量使用,作用就是消除 Python 装饰器的一些负面作用

学习functools库中装饰器相关函数的基础是 Python中装饰器的使用以及使用装饰器可能带来的副作用,具体可以查看这篇文章:Python系列之装饰器的使用。简单来书就是Python中的装饰器虽然可以在不修改原始函数的基础上动态地修改函数的功能,但使用之后函数的名称就会发生改变,而如果想去除这种副作用就需要使用functools中的相关函数.

wraps

1
2
3
4
5
6
7
8
9
10
11
12
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

@decorator
def add(x, y):
return x + y

# 查看函数的名称
add.__name__
'wrapper'

通过上述示例可以看出,函数在经过装饰器装饰之后,其名称会变为装饰器返回的函数名称而不是原本的函数名称。为了消除这个副作用,可以在装饰器中再添加一个装饰器wraps

1
2
3
4
5
6
7
8
9
10
11
12
13
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

@decorator
def add(x, y):
return x + y

# 再次查看函数名称
add.__name__
'add'

添加了wraps装饰器之后,会更正的属性定义在 WRAPPER_ASSIGNMENTS 中:

1
2
3
# 添加wraps之后,函数的以下属性会被更正
functools.WRAPPER_ASSIGNMENTS
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')


update_wrapper

update_wrapper 的作用与 wraps 类似,不过功能更加强大,换句话说,wraps 其实是 update_wrapper 的特殊化,实际上 wraps(wrapped) 相当于 partial(update_wrapper, wrapped=wrapped, **kwargs)

因此,上面的代码可以用 update_wrapper 重写如下:

1
2
3
4
def decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return update_wrapper(wrapper, func)


reduce-化简函数

reduce 函数的使用形式如下:

1
reduce(function, sequence[, initial])

具体来说,reduce函数先将 sequence 的前两个 item 传给 function,即 function(item1, item2),函数的返回值和 sequence 的下一个 item 再传给 function,即 function(function(item1, item2), item3),如此迭代,直到 sequence 没有元素,如果有 initial,则作为初始值调用。

也就是说:

1
reduece(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

使用示例:

1
2
3
4
from functools import reduce
# 相当于 ((1 * 2) * 3) * 4
reduce(lambda x, y: x * y, [1, 2, 3, 4])
24


比较相关

cmp_to_key

Python2sorted命令中支持一个参数为cmp,用来定义对象之间的比较函数,但 Python3 为了语言的简洁性去掉了 cmp 这个参数,在 Python3 中自定义的比较函数需要通过 cmp_to_key 将比较函数转化成键函数,这个函数可以传递给参数key,具体的实现方式这里就不学习了,下面来看一个示例:

1
2
3
from functools import cmp_to_key
sorted(range(5), key=cmp_to_key(lambda x, y: y-x))
[4, 3, 2, 1, 0]

支持使用cmp_to_key的函数包括:sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()

这里补充一下比较函数和键函数的区别

  • 比较函数是任何可调用的对象,它会接受两个参数,比较它们并根据所提供的参数顺序返回一个数字。负数表示第一个参数小于第二个参数,零表示它们相等,正数表示第一个参数大于第二个参数。一个简单实现可能是这样的:

    1
    2
    3
    4
    5
    6
    7
    def compare(a,b):
    if a<b:
    return (-1)
    elif a==b:
    return (0)
    else:
    return (1)
  • 键函数是一个可调用对象,它接受一个参数并返回另一个用作排序键的值。

官方文档

A comparison function is any callable that accept two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than. A key function is a callable that accepts one argument and returns another value to be used as the sort key.


参考链接



-----本文结束感谢您的阅读-----

本文标题:Python系列之functools-控制函数的工具

文章作者:showteeth

发布时间:2020年05月23日 - 09:19

最后更新:2020年05月24日 - 09:43

原始链接:http://showteeth.tech/posts/1848.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%