Python标准库之 functools

巨人的肩膀

Python标准库之 functools/itertools/operator

凉菜

functools, itertools, operator是Python标准库为我们提供的支持函数式编程的三大模块,合理的使用这三个模块,我们可以写出更加简洁可读的Pythonic代码,接下来我们通过一些example来了解三大模块的使用。

functools的使用

functools是Python中很重要的模块,它提供了一些非常有用的高阶函数。高阶函数就是说一个可以接受函数作为参数或者以函数作为返回值的函数,因为Python中函数也是对象,因此很容易支持这样的函数式特性。

partial

>>>from functools import partial

>>>basetwo = partial(int, base=2)

>>>basetwo('10010')
18

basetwo(‘10010’)实际上等价于调用int(‘10010’, base=2),当函数的参数个数太多的时候,可以通过使用functools.partial来创建一个新的函数来简化逻辑从而增强代码的可读性,而partial内部实际上就是通过一个简单的闭包来实现的。

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*args, *fargs, **newkeywords)

    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords

    return newfunc

partialmethod

partialmethod和partial类似,但是对于绑定一个非对象自身的方法的时候,这个时候就只能使用partialmethod了,我们通过下面这个例子来看一下两者的差异。

import functools


def standalone(self, a=1, b=2):
    print(' called standalone with:', (self, a, b))
    if self is not None:
        print(' self.attr =', self.attr)


class MyClass:
    """Demonstration class for functools"""

    def __init__(self):
        self.attr = 'instance attribute'


method1 = functools.partialmethod(standalone)  # 使用partialmethod
method2 = functools.partial(standalone)  # 使用partial1

>>>o = MyClass()

>>>o.method1()

called standalone with: (<__main__.myclass object at>, 1, 2)

self.attr = instance attribute

>>>o.method2()

Traceback (most recent call last):

File "", line 1, in

TypeError: standalone() missing 1 required positional argument: 'self'

singledispatch

虽然Python不支持同名方法允许有不同的参数类型,但是我们可以借用singledispatch来动态指定相应的方法所接收的参数类型,而不用把参数判断放到方法内部去判断从而降低代码的可读性。

针对函数的代码

from functools import singledispatch


class TestClass(object):
    @singledispatch
    def test_method(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")
        print(arg)

    @test_method.register(int)
    def _(arg):
        print("Strength in numbers, eh?", end=" ")
        print(arg)

    @test_method.register(list)
    def _(arg):
        print("Enumerate this:")
        for i, elem in enumerate(arg):
            print(i, elem)

下面通过@test_method.register(int)和@test_method.register(list)指定当test_method的第一个参数为int或者list的时候,分别调用不同的方法来进行处理。

>>> TestClass.test_method(55555)

Strength in numbers, eh? 55555

>>> TestClass.test_method([33, 22, 11])

Enumerate this:

0 33

1 22

2 11

>>> TestClass.test_method('hello world', verbose=True)

Let me just say, hello world

针对类的代码

from functools import singledispatch, update_wrapper


def methdispatch(func):
    dispatcher = singledispatch(func)

    def wrapper(*args, **kw):
        return dispatcher.dispatch(args[1].__class__)(*args, **kw)

    wrapper.register = dispatcher.register
    update_wrapper(wrapper, func)
    return wrapper

    @methdispatch
    def extract_value(self, value):
        print(f' 传输参数类型为:{type(value)},不是有效类型')

    @extract_value.register(str)
    def _(self, value: str):
        """
        对数值型字符串的数字抽取, 并返回浮点数
        :param value:
        :return:
        """
        new_str = ''  # 如果提取不出数值,则返回空
        try:
            new_str = float(value)
        except ValueError:
            res = re.match(PATTERN_PURE_DIGIT, value)
            if res is not None:
                new_str = float(res.group(1))
        return new_str

    @extract_value.register(list)
    def _(self, value: list):
        # 能够处理纯数字的字符串, 同时也可以处理,传进来的值为列表
        value_float_list = []  # 用来处理异常的情况而保留的列表
        try:
            value_float_list = list(map(lambda x: float(x), value))  # 如果出错代表携带单位
        except ValueError:
            # 调用正则提取相应数值
            for each in value:  # each 为str 类型,并且提取其中float 字符
                value_float_list.append(self.extract_value(each))

        return value_float_list

wraps

装饰器会遗失被装饰函数的__name__和__doc__等属性,可以使用@wraps来恢复。

def my_decorator(f):
    @wraps(f)
    def wrapper():
        """wrapper_doc"""

        print('Calling decorated function')
        return f()

    return wrapper


@my_decorator
def example():
    """example_doc"""

    print('Called example function')
>>>example.__name__

'example'

>>>example.__doc__

'example_doc'

# 尝试去掉@wraps(f)来看一下运行结果,example自身的__name__和__doc__都已经丧失了

>>>example.__name__

'wrapper'

>>>example.__doc__

'wrapper_doc'

我们也可以使用update_wrapper来改写:

from functools import wraps, update_wrapper


def g():
    ...


g = update_wrapper(g, f)


# equal to
@wraps(f)
def g():
    ...

@wraps内部实际上就是基于update_wrapper来实现的。

from functools import update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES


def wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES):
    def decorator(wrapper):
        return update_wrapper(wrapper, wrapped=wrapped...)

    return decorator

lru_cache

lru_cache和singledispatch是开发中应用非常广泛的黑魔法,接下来我们来看一下lru_cache。对于重复的计算性任务,使用缓存加速是非常重要的,下面我们通过一个fibonacci的例子来看一下使用lru_cache与不使用lru_cache在速度上的差异。

# clockdeco.py
import time
import functools


def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
        return result

    return clocked

不使用lru_cache

from clockdeco import clock

@clock

def fibonacci(n):

    if n < 2:
    
        return n
    
    return fibonacci(n-2) + fibonacci(n-1)

if __name__=='__main__':

    print(fibonacci(6))

下面是运行结果,从运行结果可以看出fibonacci(n)会在递归的时候被重复计算,这是非常耗时消费资源的。

[0.00000119s] fibonacci(0) -> 0

[0.00000143s] fibonacci(1) -> 1

[0.00021172s] fibonacci(2) -> 1

[0.00000072s] fibonacci(1) -> 1

[0.00000095s] fibonacci(0) -> 0

[0.00000095s] fibonacci(1) -> 1

[0.00011444s] fibonacci(2) -> 1

[0.00022793s] fibonacci(3) -> 2

[0.00055265s] fibonacci(4) -> 3

[0.00000072s] fibonacci(1) -> 1

[0.00000072s] fibonacci(0) -> 0

[0.00000095s] fibonacci(1) -> 1

[0.00011158s] fibonacci(2) -> 1

[0.00022268s] fibonacci(3) -> 2

[0.00000095s] fibonacci(0) -> 0

[0.00000095s] fibonacci(1) -> 1

[0.00011349s] fibonacci(2) -> 1

[0.00000072s] fibonacci(1) -> 1

[0.00000095s] fibonacci(0) -> 0

[0.00000095s] fibonacci(1) -> 1

[0.00010705s] fibonacci(2) -> 1

[0.00021267s] fibonacci(3) -> 2

[0.00043225s] fibonacci(4) -> 3

[0.00076509s] fibonacci(5) -> 5

[0.00142813s] fibonacci(6) -> 8

使用lru_cache

import functools

from clockdeco import clock

@functools.lru_cache() # 1
@clock # 2
def fibonacci(n):

	if n < 2:
		return n
	
	return fibonacci(n-2) + fibonacci(n-1)

if __name__=='__main__':

	print(fibonacci(6))

下面是运行结果,对于已经计算出来的结果将其放入缓存。

[0.00000095s] fibonacci(0) -> 0

[0.00005770s] fibonacci(1) -> 1

[0.00015855s] fibonacci(2) -> 1

[0.00000286s] fibonacci(3) -> 2

[0.00021124s] fibonacci(4) -> 3

[0.00000191s] fibonacci(5) -> 5

[0.00024652s] fibonacci(6) -> 8

total_ordering

Python2中可以通过自定义__cmp__的返回值0/-1/1来比较对象的大小,在Python3中废弃了__cmp__,但是我们可以通过totalordering然后修改 \_lt__() , __le__() , __gt__(), __ge__(), __eq__(), __ne__() 等魔术方法来自定义类的比较规则。p.s: 如果使用必须在类里面定义 __lt__() , __le__() , __gt__(), __ge__()中的一个,以及给类添加一个__eq__() 方法。

import functools


@functools.total_ordering
class MyObject:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        print(' testing __eq__({}, {})'.format(

            self.val, other.val))

        return self.val == other.val
        
    def __gt__(self, other):
        print(' testing __gt__({}, {})'.format(

            self.val, other.val))

        return self.val > other.val


a = MyObject(1)
b = MyObject(2)
for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:
    print('\n{:<6}:'.format(expr))

    result = eval(expr)

    print(' result of {}: {}'.format(expr, result))

下面是运行结果:

a < b :

testing __gt__(1, 2)

testing __eq__(1, 2)

result of a < b: True

a <= b:

testing __gt__(1, 2)

result of a <= b: True

a == b:

testing __eq__(1, 2)

result of a == b: False

a >= b:

testing __gt__(1, 2)

testing __eq__(1, 2)

result of a >= b: False

a > b :

testing __gt__(1, 2)

result of a > b: False
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值