zoukankan      html  css  js  c++  java
  • functools:管理函数的工具

    介绍

    functools模块提供了一些工具来管理或扩展和其他callable对象,从而不必完全重写

    修饰符

    偏函数partial

    from functools import partial
     
     
    '''
    functools模块提供的主要工具就是partial类,可以用来包装一个有默认参数的callable对象。
    得到的对象本身就是callable,可以把它看作是原来的参数。
    '''
     
     
    # 举个栗子
    def foo(name, age, gender):
        print(name, age, gender)
     
     
    p = partial(foo, "mashiro", 16)
    p("female")  # mashiro 16 female
    '''
    可以看到p相当于是已经绑定了name和age的foo函数,name我们在传参的时候只需要传入一个gender就可以了
    这个函数的源码实现比较复杂,但是如果以简单的装饰器的方式实现就很清晰了
    '''
     
     
    def my_partial(f, name, age):
        def inner(gender):
            return f(name, age, gender)
        return inner
     
     
    p = my_partial(foo, "satori", 16)
    p("female")  # satori 16 female
    '''
    可以看到,当我调用my_partial(foo, "satori", 16)的时候,返回了inner函数
    此时的p相当于是inner,当我再调用p("female")的时候,等价于调用inner("female")
    然后将两次传入的参数,按照顺序组合起来传递给foo函数,如果不限制参数的话就是:
    def my_partial(f, *args1, **kwargs1):
        def inner(*args2, **kwargs2):
            from collections import ChainMap
            args = args1 + args2
            kwargs = dict(ChainMap(kwargs1, kwargs2))
            return f(*args, **kwargs)
        return inner
         
    所以一定要和原函数的参数顺序保持一致,如果我传入p = my_partial("mashiro", 16),此时"mashiro"会传给name,16传给age
    我再调用p(name="xxx")的话,肯定会报错的,参数重复指定了
    因此务必注意参数的传递顺序。
    
    个人觉得这个偏函数最大的作用就是解决了回调函数只能传入函数名、但却又想传参时候的尴尬。
    '''
    

    可以把partial看成是一个简单的装饰器,装饰器不仅可以装饰函数,还可以装饰类,只要是callable对象,说白了只要是能加上()的都可以。这就是Python的魅力,非常的动态。比如列表进行extend, 其实不仅仅可以extend一个列表,还可以是元组,甚至是字典,只要是iterable对象都可以。

    from functools import partial
     
     
    class A:
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
     
        def print_info(self):
            print(f"name: {self.name}, age: {self.age}, gender: {self.gender}")
     
     
    p = partial(A, "mashiro", 16)
    a = p("female")  # 这两步等价于 a = A("mashiro", 16, "female")
    a.print_info()  # name: mashiro, age: 16, gender: female
    
    from functools import partial
    import functools
     
     
    '''
    默认情况下,partial对象没有__name__属性的,如果没有这些属性,那么被修饰的函数会很难调试。
    '''
     
     
    def foo():
        pass
     
     
    print(foo.__name__)  # foo
    p = partial(foo)
    try:
        print(p.__name__)
    except AttributeError as e:
        print(e)  # 'functools.partial' object has no attribute '__name__'
     
     
    # 那么如何添加呢?首先增加到包装器的属性在WRAPPER_ASSIGNMENTS中定义,另外WRAPPER_UPDATES列出了要修改的值
    print("assign:", functools.WRAPPER_ASSIGNMENTS)  # assign: ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
    print("update:", functools.WRAPPER_UPDATES)  # update: ('__dict__',)
     
    # 添加,表示从原函数将属性赋值或增加到partial对象
    functools.update_wrapper(p, foo)
    print(p.__name__)  # foo
    

    partialmethod

    partial返回一个可以直接使用的callable,partialmethod返回的callable则可以用做对象的非绑定方法

    from functools import partial, partialmethod
    
    
    def standalone(self):
        print(f"self = {self}")
    
    
    class A:
        method1 = partial(standalone)
        method2 = partialmethod(standalone)
    
    
    a = A()
    try:
        a.method1()
    except TypeError as e:
        # 由于standalone需要一个参数self,我们这里没有传,因此报错
        print(e)  # standalone() missing 1 required positional argument: 'self'
    
    # 但是我们调用method2呢?
    a.method2()  # self = <__main__.A object at 0x0000000002964588>
    '''
    得到了一个A的实例对象。
    所以,partial在哪里调用时没有区别的,必须手动显示地传递,该是几个就是几个。
    但是在类中如果使用partialmethod定义的话,那么在使用实例调用的话,会自动将实例作为第一个参数传进去。
    '''
    

    wraps

    from functools import wraps
     
     
    '''
    我们在知道在使用装饰器装饰完函数的时候,属性会变。比如:
    '''
     
     
    def deco(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner
     
     
    @deco
    def foo():
        pass
     
     
    # 函数从下到下执行,加上@deco等价于,foo = deco(foo) = inner,也就是说此时的foo不再是foo了,已经是inner了
    print(foo.__name__)  # inner
    # 那么如何在装饰的时候,还保证原来函数的信息呢
     
     
    def deco(func):
        @wraps(func)  # 只需要加上这一个装饰器即可,会自动对所修饰的函数应用update_wrapper
        def inner(*args, **kwargs):
            return func(*args, **kwargs)
        return inner
     
     
    @deco
    def bar():
        pass
     
     
    # 可以看到原来函数的信息并没有改变,不仅仅是函数名,还包括__doc__等其他元信息
    print(bar.__name__)  # bar
    

    比较

    import functools
     
     
    '''
    在Python2中,类可以有一个__cmp__()方法,它会根据这个对象小于、等于、或者大于所比较的元素而分别返回-1、0、1.
    Python2.1中引入了富比较(rich comparision)的方法。
    如:__lt__(),__gt__(),__le__(),__eq__(),__ne__(),__gt__()和__ge__(),可以完成一个比较操作并返回一个bool值。
    Python3已经废弃了__cmp__()方法。
    另外,functools提供了一些工具,从而能更容易地编写符合新要求的类,即符合Python3中新的比较需求。
    '''
     
     
    @functools.total_ordering
    class A:
        def __init__(self, val):
            self.val = val
     
        def __eq__(self, other):
            return self.val == other.val
     
        def __gt__(self, other):
            return self.val > other.val
     
     
    a1 = A(1)
    a2 = A(2)
    print(a1 < a2)  # True
     
    '''
    这个类必须提供__eq__()和另外一个富比较方法的实现,这个修饰符会自动增加其余的方法。
    '''
    

    另外还可以用于sort函数中,不过更推荐使用lambda函数,因此了解就好

    import functools
     
    '''
    由于Python3废弃了老式的比较函数,sort()之类的函数中也不再支持cmp参数。
    对于使用了比较函数的较老的程序,可以使用cmp_to_key()将比较函数转换为一个比对键的函数,这个键用于确定元素在最终序列中的位置
    '''
     
     
    def compare_obj(a, b):
        if a < b:
            return -1
        elif a > b:
            return 1
        else:
            return 0
     
     
    l = [1, 5, 2, 11, 2, 44, 54, 5, 1]
     
    print(sorted(l, key=functools.cmp_to_key(compare_obj)))  # [1, 1, 2, 2, 5, 5, 11, 44, 54]
    

    缓存

    import functools
     
    '''
    lru_cache()修饰符将一个函数包装在一个"最近最少使用的"缓存中。函数的参数用来建立一个散列键,然后映射到这个结果。
    后续调用如果有相同的参数,就会从这个缓存中获取值而不会再次调用这个函数。
    这个修饰符还会为函数增加方法来检查缓存的状态(cache_info)和清空缓存(cache_clear)
    '''
     
     
    @functools.lru_cache()  # 里面可以执行参数maxsize,默认是128
    def foo(a, b):
        print(f"foo({a} * {b})")
        return a * b
     
     
    print("第一次调用")
    for i in range(2):
        for j in range(2):
            foo(i, j)
    print(foo.cache_info())
    """
    第一次调用
    foo(0 * 0)
    foo(0 * 1)
    foo(1 * 0)
    foo(1 * 1)
    CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
    """
     
    print("
    第二次调用")
    for i in range(3):
        for j in range(3):
            foo(i, j)
    print(foo.cache_info())
    """
    第二次调用
    foo(0 * 2)
    foo(1 * 2)
    foo(2 * 0)
    foo(2 * 1)
    foo(2 * 2)
    CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)
    """
     
    print("清除缓存")  # 清除缓存
    foo.cache_clear()
    print(foo.cache_info())  # CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
     
    print("
    第三次调用")
    for i in range(2):
        for j in range(2):
            foo(i, j)
    print(foo.cache_info())
    """
    第三次调用
    foo(0 * 0)
    foo(0 * 1)
    foo(1 * 0)
    foo(1 * 1)
    CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)
    """
    # 我们观察一下第二次调用,3 * 3应该是9次,为什么只有5次,因为第一次调用有4次执行过了,放到缓存里,因此不需要执行了
    

    reduce

    import functools
     
    '''
    reduce这个函数无需介绍,在Python2中是内置的,但是在Python3中被移到functools下面
    '''
    l = range(100)
    print(functools.reduce(lambda x, y: x+y, l))  # 4950
    print(functools.reduce(lambda x, y: x+y, l, 10))  # 4960
    print(functools.reduce(lambda x, y: x+y, l, 100))  # 5050
     
     
    l = [1, 2, 3, 4, 5]
    print(functools.reduce(lambda x, y: x*y, l))  # 120
    

    泛型函数

    import functools
     
    '''
    在类似Python的动态类型语言中,通常需要基于参数的类型完成稍有不同的操作,特别是在处理元素列表与单个元素的差别时。
    直接检查参数的类型固然很简单,但是有些情况下,行为差异可能被隔离到单个的函数中。
    对于这些情况,functools提供了singledispatch修饰符来注册一组泛型函数,可以根据函数第一个参数的类型自动切换
    '''
     
     
    @functools.singledispatch
    def myfunc(arg):
        print(f"default myfunc {arg}")
     
     
    @myfunc.register(int)
    def myfunc1(arg):
        print(f"myfunc1 {arg}")
     
     
    @myfunc.register(list)
    def myfunc2(arg):
        print(f"myfunc2 {arg}")
     
     
    myfunc("string")  # default myfunc string
    myfunc(123)  # myfunc1 123
    myfunc(["1", "2"])  # myfunc2 ['1', '2']
    '''
    可以看到使用signledispatch包装的是默认实现,在未指定其他类型特定函数的时候就用这个默认实现。
    myfunc,myfunc1,myfunc2都可以调用,但是我们一般只调用被singledispatch装饰的myfunc
    其它函数则是通过myfunc.register(类型)进行注册,然后执行myfunc,根据参数类型的不同,执行不同的函数。
    比如我们注册了int、list,传入类型为int,执行myfunc1,传入list执行myfunc2。如果是没有注册的类型,那么走默认的myfunc
    '''
    
  • 相关阅读:
    (Java) LeetCode 44. Wildcard Matching —— 通配符匹配
    (Java) LeetCode 30. Substring with Concatenation of All Words —— 与所有单词相关联的字串
    (Java) LeetCode 515. Find Largest Value in Each Tree Row —— 在每个树行中找最大值
    (Java) LeetCode 433. Minimum Genetic Mutation —— 最小基因变化
    (Java) LeetCode 413. Arithmetic Slices —— 等差数列划分
    (Java) LeetCode 289. Game of Life —— 生命游戏
    (Java) LeetCode 337. House Robber III —— 打家劫舍 III
    (Java) LeetCode 213. House Robber II —— 打家劫舍 II
    (Java) LeetCode 198. House Robber —— 打家劫舍
    (Java) LeetCode 152. Maximum Product Subarray —— 乘积最大子序列
  • 原文地址:https://www.cnblogs.com/traditional/p/11872140.html
Copyright © 2011-2022 走看看