Python-functools:高阶函数和可调用对象上的操作

官方文档

目录

一. functools.cmp_to_key(func)

二. @functools.lru_cache(maxsize=128, typed=False)

三. @functools.total_ordering

四. functools.partial(func, *args, **keywords)

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

六. functools.reduce(function, iterable[, initializer])

七. @functools.singledispatch

八. @functools.wraps


一. functools.cmp_to_key(func)

        Python3中移除了sorted中的cmp关键字,但可以使用functools.cmp_to_key()对自定义的cmp函数进行包装,然后赋值给sorted中的key参数,以实现Python2中sorted的排序。同样支持key参数的还有max()、min()等函数。

        cmp指的是comparing,所以cmp函数也叫比较函数,意为一个可调用对象,该对象接受两个参数并比较它们,结果为小于则返回一个负数,相等则返回零,大于则返回一个正数。

import functools

# 定义升序的比较函数
def cmp_up(x, y):
    if x > y:
        return 1
    elif x == y:
        return 0
    else:
        return -1

# 定义降序的比较函数
def cmp_down(x, y):
    if x > y:
        return -1
    elif x == y:
        return 0
    else:
        return 1

# 使用升序的比较函数
sorted([1,2,5,21,54,7,2,2], key=functools.cmp_to_key(cmp_up))
# >> [1, 2, 2, 2, 5, 7, 21, 54]

# 使用降序的比较函数
sorted([1,2,5,21,54,7,2,2], key=functools.cmp_to_key(cmp_down))
# >> [54, 21, 7, 5, 2, 2, 2, 1]

二. @functools.lru_cache(maxsize=128, typed=False)

        一个为函数提供缓存功能的装饰器,缓存 maxsize 组传入参数,在下次以相同参数调用时直接返回上一次的结果。用以节约高开销或I/O函数的调用时间。

        由于使用了字典存储缓存,所以该函数的固定参数和关键字参数必须是可哈希的。

        不同模式的参数可能被视为不同从而产生多个缓存项,例如, f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次。

        如果 maxsize 设置为 None ,LRU功能将被禁用且缓存数量无上限。 maxsize 设置为2的幂时可获得最佳性能。

        如果 typed 设置为true,不同类型的函数参数将被分别缓存。例如, f(3) 和 f(3.0) 将被视为不同而分别缓存。

        为了衡量缓存的有效性以便调整 maxsize 形参,被装饰的函数带有一个 cache_info() 函数。当调用 cache_info() 函数时,返回一个具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize。在多线程环境中,命中数与未命中数是不完全准确的。

        该装饰器也提供了一个用于清理/使缓存失效的函数 cache_clear() 。

        原始的未经装饰的函数可以通过 __wrapped__ 属性访问。它可以用于检查、绕过缓存,或使用不同的缓存再次装饰原始函数。

        一般来说,LRU缓存只在当你想要重用之前计算的结果时使用。因此,用它缓存具有副作用的函数、需要在每次调用时创建不同、易变的对象的函数或者诸如time()或random()之类的不纯函数是没有意义的。

import functools


# 定义一个求和函数,执行期间会打印相关信息
@functools.lru_cache(maxsize=8)
def add(x, y):
    print(f"Calc {x} + {y}...")
    return x+y

# 传入两个参数1和2
print(add(1,2))
# >> Calc 1 + 2...
# >> 3

# 再次传入相同的两个参数1和2,可以看到函数没有执行,而使直接返回结果
print(add(1,2))
# >> 3

# 查看缓存状态
print(add.cache_info())
# >> CacheInfo(hits=1, misses=1, maxsize=8, currsize=1)

# 清除缓存
add.cache_clear()

三. @functools.total_ordering

        自动推导类的比较方式,比如提供了__lt__() 、__le__()、__gt__() 或 __ge__()中的一个或多个后,可以自动推导出其他比较方式。

import functools

# 定义一个支持比较方法的类,定义__lt__,然后由total_ordering自动推导其他比较方法
@functools.total_ordering
class Number():
    def __init__(self, num):
        self.num = num
        
    def __lt__(self, other):
        return self.num < other.num


# 进行比较
print(Number(2) > Number(3))
# >> False

print(Number(2) < Number(3))
# >> True

print(Number(2) == Number(3))
# >> False

四. functools.partial(func, *args, **keywords)

        提供偏函数功能,当被调用时其行为类似于 func 附带位置参数 args 和关键字参数 keywords 被调用。 如果为调用提供了更多的参数,它们会被附加到 args。 如果提供了额外的关键字参数,它们会扩展并重载 keywords。

        partial() 会被“冻结了”一部分函数参数和/或关键字的部分函数应用所使用,从而得到一个具有简化签名的新对象。 例如,partial() 可用来创建一个行为类似于 int() 函数的可调用对象,其中 base 参数默认为二:

import functools

# 定义偏函数,默认<base>为2
basetwo = functools.partial(int, base=2)
basetwo.__doc__ = 'Convert base 2 string to an int.'

print(basetwo('10010'))
# >> 18

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

        提供偏方法功能,和partial实现相同的功能,不过partialmethod是用在类中。

import functools

class Cell():
    def __init__(self):
        self._alive = False

    @property
    def alive(self):
        return self._alive

    def set_state(self, state):
        self._alive = bool(state)

    # 定义偏方法
    set_alive = functools.partialmethod(set_state, True)
    set_dead = functools.partialmethod(set_state, False)
     

c = Cell()

print(c.alive)
# >> False

# 调用偏方法
c.set_alive()

print(c.alive)
# >> True

六. functools.reduce(function, iterable[, initializer])

        对一个可迭代对象中的元素依次进行某种操作,并返回最终的结果。功能大致相当于:

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

七. @functools.singledispatch

        将一个函数转变为单一分派的泛型函数,用 @singledispatch装饰一个函数,将定义一个泛型函数。

        泛型函数是一种能够根据传入的数据类型自动推断出返回值类型的函数。

import functools

# 定义泛函数
@functools.singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("Let me just say", end=" ")
    print(arg)

        要将重载的实现添加到函数中,请使用泛型函数的 register() 属性。 它是一个装饰器。 对于带有类型标注的函数,该装饰器将自动推断第一个参数的类型:

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

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

        对于不使用类型标注的代码,可以将适当的类型参数显式地传给装饰器本身:

@fun.register(complex)
def _(arg, verbose=False):
    if verbose:
        print("Better than complicated.", end=" ")
    print(arg.real, arg.imag)

       要启用注册 lambda 和现有函数,可以使用函数形式的 register() 属性:

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

fun.register(type(None), nothing)

         在调用时,泛型函数会根据第一个参数的类型进行分派:

fun("Hello, world.")
# >> Hello, world.

fun("test.", verbose=True)
# >> Let me just say test.

fun(42, verbose=True)
# >> Strength in numbers, eh? 42

fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
# >> Enumerate this:
# >> 0 spam
# >> 1 spam
# >> # >> 2 eggs
3 spam

fun(None)
# >> Nothing.

fun(1.23)
# >> 1.23

        在没有用于特定类型的已注册实现的情况下,则会使用其方法解析顺序来查找更通用的实现。 以 @singledispatch 装饰的原始函数将为最基本的 object 类型进行注册,这意味着它将在找不到更好的实现时被使用,比如上面的"Hello, world."。

        要检查泛型函数将为给定类型选择哪个实现,请使用 dispatch() 属性:

fun.dispatch(float)
# >> <function __main__.fun(arg, verbose=False)>

fun.dispatch(dict)
# >> <function __main__.fun(arg, verbose=False)>

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

fun.registry.keys()
# >> dict_keys([<class 'object'>, <class 'int'>, <class 'list'>, <class 'complex'>, <class 'NoneType'>])

fun.registry[object]
# >> <function __main__.fun(arg, verbose=False)>

八. @functools.wraps

        @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

        它的作用是使得被装饰函数不受装饰器的影响,。unction 对象具有很多的标准属性,如 __name__、 __module__ 、__annotations__ 等,装饰器 @functools.wraps 也可以在原函数受到 wrapper 封装时,保留这些属性。

import functools

# 定义修饰器时,使用@functools.wraps
def my_decorator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwds):
        print('Calling decorated function')
        return f(*args, **kwds)
    return wrapper

# 定义函数时,使用my_decorator修饰器
@my_decorator
def example():
    """Docstring"""
    print('Called example function')

# 函数调用
example()
Calling decorated function
Called example function

# 查看函数属性(如果没有使用@functools.wraps,下面的属性会变成my_decorator的属性)
example.__name__
Out[97]: 'example'

example.__doc__
Out[98]: 'Docstring'

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值