functools是一个函数增强器,主要为高阶函数使用,作用于或者返回其他函数的函数,通常任何可调用的对象都可视为“函数”。主要包括以下几个函数:

python etree函数 python execute函数_python etree函数

cached_property

将类的方法转换为属性,该属性的值将被计算一次,然后在实例生命周期中作为常规属性进行缓存。与property()类似,但增加了缓存,对于计算复杂的属性很有用。cached_property在Python3.8之前的很多第三方库当中都有自己的实现,比如werkzeug.utils.cached_property、django.utils.functional.cached_property

举例如下:

# 在没有cached_property之前定义类属性
class DataSet:
    def __init__(self):
        self._data = None

    @property
    def data(self):
        print('开始计算数据')
        if not self._data:
            # 计算data数据
            self._data = 10 * 10
            print('计算data数据')
        return self._data

obj = DataSet()
print(obj.data)
# 输出
开始计算数据
计算data数据
100

print(obj.data)
# 输出
开始计算数据
100

使用变量记录属性数据,并在属性计算时进行判断,防止计算多次

from functools import cached_property
class DataSet:
    @cached_property
    def data(self):
        print('开始计算数据')
        return 10 * 10

obj = DataSet()
print(obj.data)
# 输出:
开始计算数据
100

print(obj.data)
# 输出:
100

可以看到,data属性函数只被计算了一次,而且无需额外定义变量计算。cached_property同时具有线程安全,在多线程中不会存在多次计算的问题。另外不支持python中的异步编程:asyncio。注意这个特性是在Python3.8中新增的。

cmp_to_key

将旧式比较功能转换为键功能。与接受关键功能的工具(例如sorted(),min(),max(),heapq.nlargest(),heapq.nsmallest(),itertools.groupby())一起使用。该函数主要用作从Python 2转换而来的程序的转换工具,该程序支持使用比较函数。

比较函数是任何可调用的函数,它们接受两个参数进行比较,小于返回一个负数,等于返回零,大于返回一个正数。键函数是一个可调用的函数,它接受一个参数并返回另一个值用作排序键。

from functools import cmp_to_key

l = [
    {
        'name': 'Tom',
        'age': 12
    },
    {
        'name': 'Join',
        'age': 52
    },
    {
        'name': 'Jeke',
        'age': 23
    }
]

def compare_func(a, b):
    if a.get('age') > b.get('age'):
        return 1 #必须返回正数,不能是True
    else:
        return -1 #必须返回负数,不能是False


print(sorted(l, key=cmp_to_key(compare_func)))
# 输出:
[{'name': 'Tom', 'age': 12}, {'name': 'Jeke', 'age': 23}, {'name': 'Join', 'age': 52}]

在python2中sorted的函数原型是:sorted(iterable, cmp=None, key=None, reverse=False),参数中包含一个cmp参数,来提供让我们传入一个自定义函数的参数,但是python3 中的sorted函数原型是:sorted(iterable, /, *, key=None, reverse=False),这里出现了/,*两个符号,上一篇我们介绍过,主要是后面没有了cmp参数,自定义函数排序就很不方便。这时候functools.cmp_to_key就为我们提供了这样一个自定义函数排序方式,将函数转换为键功能-key

lru_cache

缓存装饰器,根据参数缓存每次函数调用结果,对于相同参数的,无需重新函数计算,直接返回之前缓存的返回值

  • 如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长;当maxsize是2的幂时,LRU功能执行得最好;
  • 如果 typed设置为True, 则不同类型的函数参数将单独缓存。例如,f(3)和f(3.0)将被视为具有不同结果的不同调用;
  • 缓存是有内存存储空间限制的;
def a(x):
    print(x)
    return x+1

print(a())
# 输出:
3
4

print(a())
# 输出:
3
4

不使用缓存记录,每次都重新执行函数计算

from functools import lru_cache

@lru_cache()
def a(x):
    print(x)
    return x+1

print(a(3))
# 输出
3
4

print(a(3))
# 输出
4

print(a(4))
# 输出
4
5

使用缓存记录后,第一次a(3)调用,计算了数据后会进行缓存,第二次a(3)调用,因为参数相同,所以直接返回缓存的数据,第三次a(4)调用,因为参数不同,需要重新计算

partial

偏函数,可以扩展函数功能,但是不等于装饰器,通常应用的场景是当我们要频繁调用某个函数时,其中某些参数是已知的固定值,通常我们可以调用这个函数多次,但这样看上去似乎代码有些冗余,而偏函数的出现就是为了很少的解决这一个问题。

举一个简单的例子:

def add(a, b, c, x=1, y=2, z=3):
    return sum([a, b, c, x, y, z])

print(add(1, 2, 3, x=1, y=2, z=3))
#输出
12

如果我们频繁调用此函数,并且固定传入某些参数,比如b=20, x=100

from functools import partial

def add(a, b, c, x=1, y=2, z=3):
    print(a, b, c, x, y, z)
    return sum([a, b, c, x, y, z])

add_100 = partial(add, 20, x=100)
print(add_100(1, 2, y=2, z=3))
# 输出
20 1 2 100 2 3
128

在进行函数重新定义时,如果需要固定非关键字参数,那么默认定义的是第一个非关键字参数;如果需要固定关键字参数,直接指定关键字即可。

实际上偏函数的使用更多是在回调函数时使用,举例如下:

register_func = []

def call_back(n):
    print('call_back: ', n)

def call_back1(n, m):
    print('call_back1: ', n, m)

# 注册回调函数
register_func.append((call_back, 10))
register_func.append((call_back1, 100, 200))

# 执行回调函数
for item in register_func:
    func = item[0]
    args = item[1:]
    func(*args)

# 输出
call_back:  10
call_back1:  100 200

上面我们在注册回调函数的时候,需要记录函数名和各个参数,非常不方便,如果使用偏函数进行修饰

from functools import partial

register_func = []

def call_back(n):
    print('call_back: ', n)

def call_back1(n, m):
    print('call_back1: ', n, m)

call_back_partial = partial(call_back, 10)
call_back_partial1 = partial(call_back1, 100, 200)

# 注册回调函数
register_func.append(call_back_partial)
register_func.append(call_back_partial1)

# 执行回调函数
for func in register_func:
    func()

# 输出
call_back:  10
call_back1:  100 200

对比上面的方式,偏函数定义的优势在哪里呢?

  • 注册回调函数时,我们是知道函数参数的,所以在此使用偏函数很简单、很方便
  • 使用偏函数后,注册回调函数和调用回调函数那里都使用完全固定的写法,无论传入的是固定参数、非固定参数或者关键字参数
  • 相对于上面一点,只需要在注册的时候使用偏函数重新生成一个回调函数

这在回调函数的使用中是非常频繁、方便,而且爽就一个字

reduce

函数原型如下:

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

可以看到实际执行是将迭代器iterable中每一个元素传入function函数进行累计计算,并将最终值返回。一个简单的使用示例:

a=[1,3,5]
b=reduce(lambda x,y:x+y,a)
print(b)
# 输出
9

将a列表传入匿名函数进行累加计算

singledispatch

python函数重载,直接举例来说明

def connect(address):
    if isinstance(address, str):
        ip, port = address.split(':')
    elif isinstance(address, tuple):
        ip, port = address
	  else:
        print('地址格式不正确')

# 传入字符串
connect('123.45.32.18:8080')

# 传入元祖
connect(('123.45.32.18', 8080))

简单来说就是address可能是字符串,也可能是元组,那么我们就需要在函数内进行单独处理,如果这种类型很多呢?那就需要if...elif...elif...elif..esle...,写起来非常不美观,而且函数的可读性也会变差。

学过C++和Java的同学都知道函数重载,同样的函数名,同样的参数个数,不同的参数类型,实现多个函数,程序运行时将根据不同的参数类型自动调用对应的函数。python也提供了这样的重载方式

from functools import singledispatch

@singledispatch
def connect(address):
    print(f'传入参数类型为:{type(address)}, 不是有效的类型')

@connect.register
def connect_str(address: str):
    ip, port = address.split(':')
    print(f'参数为字符串,IP是{ip}, 端口是{port}')

@connect.register
def connect_tuple(address: tuple):
    ip, port = address
    print(f'参数为元组,IP是{ip}, 端口是{port}')

connect('123.45.32.18:8080')
# 输出
参数为字符串,IP是123.45.32.18, 端口是8080

connect(('123.45.32.18', '8080'))
# 输出
参数为元组,IP是123.45.32.18, 端口是8080

先使用singledispatch装饰器修饰connect函数,然后使用connect.register装饰器注册不同参数类型的函数(函数名可以随意,甚至不写,使用_代替),在调用的时候就会默认按照参数类型调用对应的函数执行。

total_ordering

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

被修饰的类必须至少定义 __lt__(), __le__(),__gt__(),__ge__()中的一个,同时,被修饰的类还应该提供 __eq__()方法。简单来说就是只需要重载部分运算符,装饰器就会自动帮我们实现其他的方法。

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
Traceback (most recent call last):
  File "/Volumes/Code/Python工程代码/Python基础知识/特殊特性学习/test.py", line 31, in 
    print(p1 <= p2)
TypeError: '<=' not supported between instances of 'Person' and 'Person'

报错在p1 <= p2这一行,提醒我们在Person对象之间不支持<=符号,使用total_ordering装饰器修饰以后。

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

只在类上面增加了total_ordering装饰器,就可以完美支持所有的比较运算符了

wraps

python中的装饰器是“接受函数为参数,以函数为返回值”。但是装饰器函数也会有一些负面影响。我们来看一下例子:

# 普通函数
def add(x, y):
    return x + y

print(add.__name__)
# 输出
add


# 装饰器函数
def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@decorator
def add(x, y):
    return x + y
print(add.__name__)
# 输出
wrapper

可以看到函数名发生了变化,变为装饰器函数中的wrapper,除了__name__属性外还有其他属性,定义在WRAPPER_ASSIGNMENTS和WRAPPER_UPDATES变量中,包括__module__、__name__、 __qualname__、__doc__、__annotations__、__dict__。在很多情况下,我们需要对函数进行针对性处理,必须获取函数的模块属性进行处理,这个时候,就必须消除这种负面影响。functools.wraps就为我们解决了这个问题。

from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

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

print(add.__name__)
# 输出
add

即使使用了装饰器修饰,我们仍然能获取到原函数的属性

update_wrapper

update_wrapper 的作用与 wraps 类似,不过功能更加强大,换句话说,wraps 其实是 update_wrapper 的特殊化,实际上 wraps(wrapped) 的函数源码为:

def wraps(wrapped, assigned = WRAPPER_ASSIGNMENTS, updated = WRAPPER_UPDATES):
    return partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

使用方式:

from functools import update_wrapper

def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return update_wrapper(wrapper, func)

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

print(add.__name__)
# 输出
add

注意:wraps和update_wrapper是专为装饰器函数所设计,而且强烈建议在定义装饰器时进行修饰