Python 标准库 functools 模块详解

functools 官方文档:https://docs.python.org/zh-cn/3/library/functools.html

Python 标准模块 --- functools:https://www.cnblogs.com/zhbzz2007/p/6001827.html

python常用模块 - functools 模块:https://www.cnblogs.com/su-sir/p/12516224.html

Python 的 functools 模块提供了一些常用的高阶函数,也就是用于处理其它函数的特殊函数。换言之,就是能使用该模块对 所有可调用对象( 即 参数 或(和) 返回值 为其他函数的函数 ) 进行处理。

Python 的 functools 模块的作用: 为 可调用对象(callable objects)函数 定义高阶函数或操作。简单地说,就是基于已有的函数定义新的函数。所谓高阶函数,就是以函数作为输入参数,返回也是函数。

import functools

print(functools)
print(functools.__doc__)
print(dir(functools))

'''
<module 'functools' from 'C:\\Anaconda3\\lib\\functools.py'>
functools.py - Tools for working with functions and callable objects

[
    'RLock', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
    
    '_CacheInfo', '_HashedSeq', '_NOT_FOUND', '__all__', '__builtins__', '__cached__', '__doc__',
    '__file__', '__loader__', '__name__', '__package__', '__spec__', '_c3_merge', '_c3_mro',
    '_compose_mro', '_convert', '_find_impl', '_ge_from_gt', '_ge_from_le', '_ge_from_lt', '_gt_from_ge',
    '_gt_from_le', '_gt_from_lt', '_initial_missing', '_le_from_ge', '_le_from_gt', '_le_from_lt',
    '_lru_cache_wrapper', '_lt_from_ge', '_lt_from_gt', '_lt_from_le', '_make_key', '_unwrap_partial',

    'cached_property', 'cmp_to_key', 'get_cache_token', 'lru_cache', 'namedtuple', 'partial',
    'partialmethod', 'recursive_repr', 'reduce', 'singledispatch', 'singledispatchmethod',
    'total_ordering', 'update_wrapper', 'wraps'
]
'''

通过查看 __doc__ 可知,functools 是 函数可调用对象 的一个工具模块。

python 中 _、__ 和 __xx__ 的区别:https://www.cnblogs.com/coder2012/p/4423356.html

  • 使用  _one_underline 来表示该方法或属性是私有的,不属于API;
  • 当创建一个用于 python 调用或一些特殊情况时,使用  __two_underline__ ;
  • 使用  __just_to_underlines,来避免子类的重写!

通过 dir(functools) 可以查看 functools 的成员,只需要关注没有 "_" 的成员即可。这里主要介绍一下几个的用法:

  • functools.cmp_to_key,
  • functools.total_ordering,
  • functools.reduce,
  • functools.partial,
  • functools.update_wrapper
  • functools.wraps

functools 模块里面放了很多的工具函数,常用到的两个:

  • partial 函数 (偏函数)绑定了一部分参数的函数。作用就是少传参数,更短,更简洁。

  • wraps 函数:避免多个函数被两个装饰器装饰时就报错,因为两个函数名一样,第二个函数再去装饰的话就报错,最好是加上这个,代码更加健壮

functools.cmp_to_key()

语法:functools.cmp_to_key(func)

该函数用于将 旧式的比较函数 转换为 关键字函数

  • 旧式的比较函数:接收两个参数,返回比较的结果。返回值小于零则前者小于后者,返回值大于零则相反,返回值等于零则两者相等。
  • 关键字函数:接收一个参数,返回其对应的可比较对象。例如 sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby() 都可作为关键字函数。

在 Python 3 中,有很多地方都不再支持旧式的比较函数,此时可以使用 cmp_to_key() 进行转换。

示例:

from functools import cmp_to_key


def compare(ele1, ele2):
    return ele2 - ele1


a = [2, 3, 1]
# sorted(iterable, key=cmp_to_key(cmp_func))
print(sorted(a, key=cmp_to_key(compare)))

'''
[3, 2, 1]
'''

functools.total_ordering()

语法:functools.total_ordering(cls)

这是一个类装饰器用于自动实现类的比较运算

给定一个类,这个类定义了一个或者多个比较排序方法,这个类装饰器将会补充其余的比较方法,减少了自己定义所有比较方法时的工作量。

被修饰的类必须至少定义  __lt__(), __le__(),__gt__(),__ge__() 中的一个,同时,被修饰的类还应该提供 __eq__()方法

from functools import total_ordering


@total_ordering
class Person:
    # 定义相等的比较函数
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))

    # 定义小于的比较函数
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))


p1 = Person()
p2 = Person()

p1.lastname = "123"
p1.firstname = "000"

p2.lastname = "1231"
p2.firstname = "000"

print(p1 < p2)
print(p1 <= p2)  #
print(p1 == p2)
print(p1 > p2)
print(p1 >= p2)  #

'''
True
True
False
False
False
'''

functools.reduce()

语法:functools.reduce(function, iterable[, initializer])

该函数与 Python 内置的 reduce() 函数相同,主要用于编写兼容 Python 3 的代码。

大致等价于:

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

示例:

import functools

a = range(1, 6)
print(functools.reduce(lambda x, y: x + y, a))  # 15

functools.partial()

语法:functools.partial(func[, *args][, **keywords])

partial() 创建可调用的 partial 对象。它们有三个只读属性:

  • partial.func: 一个可调用的对象或函数。调用partial对象会转为使用新的参数和关键字参数调用func。
  • partial.args: 最左边的位置参数会优先作为位置参数提供给 partial 对象调用。
  • partial.keywords: partial 对象被调用时提供关键字参数。

partial对象与函数对象类似,它们可以被调用,有弱引用,并且可以有属性。但有一些重要的区别。对于实例,__name__和__doc__属性不会自动创建。同时,在类中定义的partial对象的行为类似静态方法,在实例属性查找时,不会转换为绑定方法。

partial函数 返回一个新的 partial对象。调用 partial 对象调用被修饰的函数func 相同,只不过调用 partial 对象时传入的参数个数通常要少于调用 func 时传入的参数个数。当一个函数 func 可以接收很多参数,而某一次使用只需要更改其中的一部分参数,其他的参数都保持不变时,partial 对象就可以将这些不变的对象冻结起来,这样调用 partial 对象时传入未冻结的参数,partial 对象调用 func 时连同已经被冻结的参数一同传给 func 函数,从而可以简化调用过程。

如果调用 partial 对象时提供了更多的参数,那么他们会被添加到 args 的后面,如果提供了更多的关键字参数,那么它们将扩展或者覆盖已经冻结的关键字参数。

partial() 函数的等价实现大致如下:

from functools import total_ordering


def partial(func, *args, **keywords):
    def new_func(*f_args, **f_keywords):
        new_keywords = keywords.copy()
        new_keywords.update(f_keywords)
        return func(*(args + f_args), **new_keywords)

    new_func.func = func
    new_func.args = args
    new_func.keywords = keywords
    return new_func

partial() 函数主要用于 "冻结" 函数的部分参数,返回一个参数更少、使用更简单的函数对象。

应用场景:函数在执行时,要带上所有必要的参数进行调用,但是有的参数可以在函数被调用之前提前获知,这种情况下,提前获知的函数参数可以提前用上,以便函数能用更少的参数进行调用。

示例:

import functools


def add(a, b):
    return a + b


add3 = functools.partial(add, 3)
add5 = functools.partial(add, 5)

print(add3(4))
print(add5(10))

'''
7
15
'''

示例:

from functools import partial

# print(int.__doc__)
"""
    int([x]) -> integer
    int(x, base=10) -> integer

    Convert a number or string to an integer, or return 0 if no arguments
    are given.  If x is a number, return x.__int__().  For floating point
    numbers, this truncates towards zero.

    If x is not a number or if base is given, then x must be a string,
    bytes, or bytearray instance representing an integer literal in the
    given base.  The literal can be preceded by '+' or '-' and be surrounded
    by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.
    Base 0 means to interpret the base from the string as an integer literal.
    >>> int('0b100', base=0)
    4
"""
base_two = partial(int, base=2)
base_two.doc = 'Convert base 2 string to an int.'
base_two('10010')  # 把 二进制字符串 10010 转换为 整数 18
print(base_two('10010'))  # 18

从中可以看出,唯一要注意的是可选参数必须写出参数名。

# partial函数(偏函数) 
from functools import partial

# 案例1
def add(a, b):
    return a + b

add(1, 2)
# 3
plus3 = partial(add, 1)
plus5 = partial(add, 2)

plus3(2)
# 3
plus5(1)
# 3

# 案例2
list1 = [23, 53, 34, 123]
partial_sorted = partial(sorted, reverse=True)
partial_sorted(list1)
# [123, 53, 34, 23]

# 案例3
def show_arg(*args, **kw):
    print(args)
    print(kw)

p1 = partial(show_arg, 1, 2, 3)
p1()
# (1, 2, 3)
# {}

p1(4, 5, 6)  # 上面已经冻结了三个参数 1,2,3 所以这里 4,5,6 直接追加
# (1, 2, 3, 4, 5, 6)
# {}

p1(a='python', b='C++')
# (1, 2, 3)
# {'a': 'python', 'b': 'C++'}

p2 = partial(show_arg, a=3, b='linux')
p2()
# ()
# {'a': 3, 'b': 'linux'}

p2(1, 2)
# (1, 2)
# {'a': 3, 'b': 'linux'}

p2(a='python', b='C++')
# ()
# {'a': 'python', 'b': 'C++'}

示例:

urlunquote = functools.partial(urlunquote, encoding='latin1')

当调用 urlunquote(args, *kargs)

相当于 urlunquote(args, *kargs, encoding='latin1')

functools.partialmethod(func, *args, **keywords)

类方法 partialmethod 是版本3.4中新增。

注意:函数方法 的区别:

  • 函数:就是定义的函数
  • 方法类里面定义的函数,叫做 方法

partialmethod  返回一个行为类似 partial 的 新partialmethod 描述符,除了它是用于方法定义,而不是直接调用。

func 必须是一个 descriptor 或者 可调用对象(两个对象都像常规函数一样作为descriptor)。

  • 当 func 是一个 descriptor (比如普遍的Python函数,classmethod()staticmethod()abstractmethod(),或者 其它 partialmethod 实例时,get 的调用会委托给底层的 descriptor,并返回一个适当的 partial 对象。
  • 当 func 不是可调用的 descriptor 时,会动态创建一个适当的绑定方法。用于方法时,该行为类似普通的 Python 函数:self 参数会插入为第一个位置参数,甚至在传递给 partialmethod 构造器的 args 和 keywords 之前。

示例:

from functools import partialmethod


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()
print(c.alive)  # False

c.set_alive()
print(c.alive)  # True


 

functools.update_wrapper()

语法:functools.update_wrapper ( wrapper, wrapped [, assigned] [, updated] )

该函数用于 更新 包装函数(wrapper),使它看起来像原函数一样。即 更新 一个包裹(wrapper)函数,使其看起来更像被包裹(wrapped)的函数

update_wrapper 函数 可以把 被封装函数的 __name__、__module__、__doc__ 和  __dict__ 都复制到封装函数去。

该函数主要用于装饰器函数的定义中,置于包装函数之前。如果没有对包装函数进行更新,那么被装饰后的函数所具有的元信息就会变为包装函数的元信息,而不是原函数的元信息。

这个函数的主要用途是在一个装饰器中,原函数会被装饰(包裹),装饰器函数会返回一个wrapper函数,如果装饰器返回的这个wrapper函数没有被更新,那么它的一些元数据更多的是反映wrapper函数定义的特征,无法反映wrapped函数的特性。

# -*- coding: gbk -*-


def this_is_living(fun):
    def living(*args, **kw):
        return fun(*args, **kw) + '活着就是吃嘛。'
    return living


@this_is_living
def what_is_living():
    """什么是活着"""
    return '对啊,怎样才算活着呢?'


print(what_is_living())
print(what_is_living.__doc__)


from functools import update_wrapper


def this_is_living(fun):
    def living(*args, **kw):
        return fun(*args, **kw) + '活着就是吃嘛。'
    return update_wrapper(living, fun)


@this_is_living
def what_is_living():
    """什么是活着"""
    return '对啊,怎样才算活着呢?'


print(what_is_living())
print(what_is_living.__doc__)

'''
对啊,怎样才算活着呢?活着就是吃嘛。
None
对啊,怎样才算活着呢?活着就是吃嘛。
什么是活着
'''

不过也没多大用处,毕竟只是少写了4行赋值语句而已。

可选的参数是一个元组,指定了被包裹函数的哪些属性直接赋值给包裹函数的对应属性,同时包裹函数的哪些属性要更新而不是直接接受被包裹函数的对应属性。

  • assigned 元组 指定要直接使用原函数的值进行替换的属性。默认值是模块级别的常量 WRAPPER_ASSIGNMENTS将被包裹函数的 __name__, __module__,和 __doc__ 属性赋值给包裹函数)。
  • updated 元组 指定要对照原函数进行更新的属性。默认值是模块级别的常量 WRAPPER_UPDATES默认更新wrapper函数的 __dict__ 属性)。

默认 partial对象 ( 即 偏函数对象 )  没有 name 和 doc,这种情况下,对于装饰器函数非常难以 debug。使用 update_wrapper() 从原始对象拷贝并加入现有 partial 对象,它可以把被封装函数的 namemoduledoc 和 dict 都复制到封装函数去 ( 模块级别常量 WRAPPER_ASSIGNMENTSWRAPPER_UPDATES)

import functools

print(functools.WRAPPER_ASSIGNMENTS)
print(functools.WRAPPER_UPDATES)

'''
('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
('__dict__',)
'''

这个函数主要用在装饰器函数中,装饰器返回函数反射得到的是包装函数的函数定义而不是原始函数定义

import functools


def wrap_1(func):
    def call_it(*args, **kwargs):
        """wrap_1 func: call_it"""
        print('[wrap_1][call_it] before call')
        return func(*args, *kwargs)

    return call_it


@wrap_1
def hello_1():
    """hello_1 : print hello world"""
    print('hello_1 world')


def wrap_2(func):
    def call_it(*args, **kwargs):
        """wrap_2 func: call_it"""
        print('[wrap_2][call_it] before call')
        return func(*args, *kwargs)

    return functools.update_wrapper(call_it, func)


@wrap_2
def hello_2():
    """hello_2 : print hello world"""
    print('hello_2 world')


if __name__ == '__main__':
    hello_1()
    print(hello_1.__name__)  # 没装饰之前,这里打印的不是 hello_1 的 __name__
    print(hello_1.__doc__)
    print('*' * 50)
    hello_2()
    print(hello_2.__name__)  # 装饰之后,这里才输出 hello_2 的真正 __name__
    print(hello_2.__doc__)

'''
[wrap_1][call_it] before call
hello_1 world
call_it
wrap_1 func: call_it
**************************************************
[wrap_2][call_it] before call
hello_2 world
hello_2
hello_2 : print hello world
'''

functools.wraps()

语法:functools.wraps ( wrapped [, assigned] [, updated] )

wraps() 简化了 update_wrapper() 函数的调用,即 wraps 函数 把 update_wrapper 也封装了进来。

它等价于 partial(update_wrapper, wrapped=wrapped, assigned, updated=updated)。

# -*- coding: gbk -*-

from functools import wraps


def this_is_living(fun):
    @wraps(fun)
    def living(*args, **kw):
        return fun(*args, **kw) + '活着就是吃嘛。'

    return living


@this_is_living
def what_is_living():
    """什么是活着"""
    return '对啊,怎样才算活着呢?'


print(what_is_living())
print(what_is_living.__doc__)

'''
对啊,怎样才算活着呢?活着就是吃嘛。
什么是活着
'''

示例:

from functools import wraps


def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        print("Calling decorated function")
        return f(*args, **kwargs)

    return wrapper


@my_decorator
def example():
    """DocString"""
    print("Called example function")


example()
print(example.__name__)
print(example.__doc__)

'''
Calling decorated function
Called example function
example
DocString
'''

可以看到,最终调用函数 example 时,是经过 @my_decorator 装饰的,装饰器的作用是接受一个被包裹的函数作为参数,对其进行加工,返回一个包裹函数,代码使用 @functools.wraps 装饰将要返回的包裹函数 wrapper,使得它的  __name__, __module__,和  __doc__  属性与被装饰函数 example 完全相同,这样虽然最终调用的是经过装饰的 example 函数,但是某些属性还是得到维护。

如果在 @my_decorator 的定义中不使用 @function.wraps 装饰包裹函数,那么最终 example.__name__  将会变成 'wrapper',而 example.__doc__  也会丢失。

将 @wraps(f) 注释掉,然后运行程序,控制台输出,

Calling decorated function
Called example function
wrapper
None

wraps 其实就是 调用函数装饰器 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) 的简写

import functools


def wrap_3(func):

    @functools.wraps(func)
    def call_it(*args, **kwargs):
        """wrap_3 func : call_it"""
        print('[wrap_3][call_it] before call')
        return func(*args, *kwargs)
    return call_it


@wrap_3
def hello_3():
    """hello_3 : print hello world"""
    print('hello_3 world')


if __name__ == '__main__':
    hello_3()
    print(hello_3.__name__)  #
    print(hello_3.__doc__)


'''
[wrap_3][call_it] before call
hello_3 world
hello_3
hello_3 : print hello world
'''

functools.singledispatch(default)

版本3.4中新增。将函数转换为 single-dispatch generic 函数。

使用 @singledispatch 装饰器定义 generic函数。注意,dispatch 发生在第一个参数的类型上,相应的创建函数:

from functools import singledispatch


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print('Let me just say,', end=' ')
    print(arg)

使用 generic 函数的 register()属性添加函数的重载实现。这是一个装饰器,接受一个类型参数,并装饰实现该类型操作的函数:

@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print('Strength in numbers, eh?', end=' ')
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print('Enumerate this:')
    for i, elem in enumerate(arg):
        print(i, elem)

为了能够注册 lambda 表达式和预先存在的函数,register()可以用于函数形式:

def nothing(arg, verbose=False):
    print('Nothing.')


fun.register(type(None), nothing)

register()属性返回未装饰的函数,可以使用装饰堆叠,pickling,以及为每个变体单独创建单元测试:

from functools import singledispatch
from decimal import Decimal


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print('Let me just say,', end=' ')
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print('Strength in numbers, eh?', end=' ')
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print('Enumerate this:')
    for i, elem in enumerate(arg):
        print(i, elem)


def nothing(arg, verbose=False):
    print('Nothing.')


fun.register(type(None), nothing)


@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print('Half of your number:', end=' ')
    print(arg / 2)


print(fun_num is fun)  # False

调用时,generic 函数根据第一个参数的类型 dispatch

from functools import singledispatch
from decimal import Decimal


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print('Let me just say,', end=' ')
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print('Strength in numbers, eh?', end=' ')
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print('Enumerate this:')
    for i, elem in enumerate(arg):
        print(i, elem)


def nothing(arg, verbose=False):
    print('Nothing.')


fun.register(type(None), nothing)


@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print('Half of your number:', end=' ')
    print(arg / 2)


print(fun_num is fun)              # False
print(fun('Hello World.'))         # Hello World.
print(fun('test.', verbose=True))  # Let me just say, test.
print(fun(42, verbose=True))       # Strength in numbers, eh? 42
print(fun(['spam', 'spam', 'eggs', 'spam'], verbose=True))
print(fun(None))                   # Nothing.
print(fun(1.23))                   # 0.615

'''
False
Hello World.
None
Let me just say, test.
None
Strength in numbers, eh? 42
None
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
None
Nothing.
None
0.615
None
'''

当没有注册特定类型的实现时,其方法解析顺序用于查找更通用的实现。用@singledispatch装饰的原始函数是为object类型注册的,如果没有找到更好的实现,则使用它。

使用dispatch()属性查看generic函数为指定类型选择哪个实现:

from functools import singledispatch
from decimal import Decimal


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print('Let me just say,', end=' ')
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print('Strength in numbers, eh?', end=' ')
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print('Enumerate this:')
    for i, elem in enumerate(arg):
        print(i, elem)


def nothing(arg, verbose=False):
    print('Nothing.')


fun.register(type(None), nothing)


@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print('Half of your number:', end=' ')
    print(arg / 2)


print(fun.dispatch(float))
print(fun.dispatch(dict))

'''
<function fun_num at 0x0000028C00FE0D30>
<function fun at 0x0000028C7C0E6280>
'''

使用只读属性 registry 访问所有注册的实现:

from functools import singledispatch
from decimal import Decimal


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print('Let me just say,', end=' ')
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print('Strength in numbers, eh?', end=' ')
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print('Enumerate this:')
    for i, elem in enumerate(arg):
        print(i, elem)


def nothing(arg, verbose=False):
    print('Nothing.')


fun.register(type(None), nothing)


@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
    if verbose:
        print('Half of your number:', end=' ')
    print(arg / 2)


print(fun.registry.keys())
print(fun.registry[float])
print(fun.registry[object])

'''
dict_keys([<class 'object'>, <class 'int'>, <class 'list'>, <class 'NoneType'>, 
           <class 'decimal.Decimal'>, <class 'float'>])
<function fun_num at 0x000001B3FDB80D30>
<function fun at 0x000001B3F8C86280>
'''

猜你喜欢

转载自blog.csdn.net/freeking101/article/details/109662542