美文网首页
python functools

python functools

作者: 天空蓝雨 | 来源:发表于2020-06-08 14:15 被阅读0次

    官方文档(机基于最新的py3.8,以前版本可能有些函数没有,比如cached_property):
    https://docs.python.org/zh-cn/3/library/functools.html
    小试牛刀:
    偏函数:

    import functools
    def fun(a, b, c):   
        print("a is %d\nb is %d \nc is %d" %(a, b, c))
    f = functools.partial(fun, b = 2)
    f(1, c=2, b=3)
    >> 
    a is 1
    b is 3 
    c is 2
    

    先说一个简单的知识点,也是以前在 py 小知识点总结过的。
    就是 默认参数肯定是在 位置参数后面的
    穿参的时候,可以都是用 关键字的形式传过去,这时候如果是位置参数就会把形参的名字和关键字参数对应(默认是按照位置赋值的),这种穿参形式,也要注意也要符合 传的位置参数(传的时候不写 = 这种形式)要放在前面的原则。 否则汇报错的哦。

    关键字参数一定要在位置参数的后面,无论是定义参数还是调用函数穿参
    关键字参数一定要在位置参数的后面,无论是定义参数还是调用函数穿参
    (如果调用的时候,都加 = 这种参数形式,那顺序就是摆设了,随便放都可以)
    好了上面的东西对于理解 偏函数会有一定帮助的。
    下面介绍functools:

    functools 适用于 高阶函数。即参数或(和)返回值为其他函数的函数

    就如上面的偏函数一样,返回的是一个新的函数对象

    偏 函数 partial

    就是 偏函数:
    partial 函数 大致原理(来自官方的举例)

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

    可以看出,partial 接受三个参数,分别是 旧函数对象, 可变参数, 关键字参数。并且用闭包形式,把旧函数对象,的参数进行扩充了。 位置参数他是放到了旧函数的左边,关键字参数进行合并。


    partial 介绍

    其实上面的 newfunc.xx = xx,大可不必写。因为闭包可以返回到 newfunc 里面(可能我的理解片面)
    (注意虽然像装饰器,但是写成装饰器无法工作,因为是二级装饰器,无法穿参)
    这里也学习了一点就是,普通函数也是可以像类一样 使用 . 操作符的。

    有意思

    装饰器 修饰 wraps

    @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
    这个用过了,就是 让 被装饰 函数函数签名保持不变(不加的话就变成了 装饰器里面的那个函数的签名了,虽然其功能不受音响),在使用装饰器的时候,最好写这个(新手请忽略)

    这是一个便捷函数,用于在定义包装器函数时发起调用 update_wrapper() 作为函数装饰器。 它等价于 partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated)

    举个例子:

    >>> from functools import wraps
    >>> def my_decorator(f):
    ...     @wraps(f)
    ...     def wrapper(*args, **kwds):
                ""'这是 wrapper"""
    ...         print('Calling decorated function')
    ...         return f(*args, **kwds)
    ...     return wrapper
    ...
    >>> @my_decorator
    ... def example():
    ...     """Docstring"""
    ...     print('Called example function')
    ...
    >>> example()
    Calling decorated function
    Called example function
    >>> example.__name__
    'example'
    >>> example.__doc__
    'Docstring'
    
    不加 wraps(f)
    >>> example.__name__
    wrapper
    >>> example.__doc__
    "这是 wrapper"
    

    functools 还有 其他功能,后面用的时候总结吧:

    • @functools.cached_property(func) # 3.8 新增功能
      就是和 property 类似 把函数转化为一个 属性,但是增加了缓存功能

    一次性计算该特征属性的值,然后将其缓存为实例生命周期内的普通属性
    对于高频使用到的固定属性,这个就非常好用了。
    每个实例上的 dict 属性是可变的映射(不适用于,元类等 没有提供 dict 属性的类)

    大概原理就是,把实例使用 cached_property 装饰的 函数属性,算出来的值直接放到 实例的 dict 属性里面,下次取的时候,就不用算了,直接从 dict 里面拿就行了
    举个例子,一个实例的声明周期内,调用很多 函数属性,会提高计算速度, 每个实例,只会计算一次 函数属性

    • 使用 cached_property (如果是新的实例,他依然会计算一次)
    import statistics
    from functools import lru_cache, cached_property
    
    class DataSet:
        """ 使用 cached_property"""
        def __init__(self, sequence_of_numbers):
            self._data = sequence_of_numbers
    
        @cached_property
        def stdev(self):
            return statistics.stdev(self._data)  # 计算标准偏差
    
        @cached_property
        def variance(self):
            return statistics.variance(self._data)  # 样本方差
    
    class DataSetOld:
        """不使用 cached_property"""
        def __init__(self, sequence_of_numbers):
            self._data = sequence_of_numbers
        @property
        def stdev(self):
            return statistics.stdev(self._data)  # 计算标准偏差
    
        @property
        def variance(self):
            return statistics.variance(self._data) # 样本方差
    
    
    import time
    start = time.time()
    new_li = DataSet([0, 100, 1.3])
    old_li = DataSetOld([0, 100, 1.3])
    for i in range(1000):
        _ = new_li.stdev
        __ = new_li.variance
    print(new_li.stdev, new_li.variance)
    end = time.time()
    print("使用 cached_property:", end - start)
    
    start = time.time()
    for i in range(1000):
        _ = old_li.stdev
        __ = old_li.variance
    print(old_li.stdev, old_li.variance)
    end = time.time()
    print("不使用 cached_property:",end-start)
    
    >> 57.3634320219191 3290.5633333333335
    使用 cached_property: 0.00194549560546875
    57.3634320219191 3290.5633333333335
    不使用 cached_property: 0.7226638793945312
    
    • functools.cmp_to_key(func)
    • @functools.lru_cache(user_function) # 同下个
    • @functools.lru_cache(maxsize=128, typed=False)
      在Python 3.7或更低版​​本上,您必须做@lru_cache()(在@lru_cache之后加上括号)
      就是为 一个函数提供缓存功能,如果这个函数下次调用传入的参数一模一样,就直接返回那次相同参数的结果, 节约 高 IO 开销等,提高速度。6
      注意参数不同的规则:

    f(a=1, b=2) 和 f(b=2, a=1) 因其参数顺序不同,可能会被缓存两次

    缓存 maxsize 组传入参数,默认是缓存 128 组结果,因该够用了, 超过也会有个刷新机制,应该是把 不常用的结果给移除。
    typed : 是不是 严格区分参数, 比如typed=False, 会把 1.0 和1 当作相同的参数
    不用 lru_cache

    import time
    # @lru_cache
    def test_lru_cache(a, b):
        time.sleep(3)
    
        return a+b
    
    start = time.time()
    test_lru_cache(1, 2)
    test_lru_cache(1, 2)
    test_lru_cache(1, 2)
    print(test_lru_cache(1, 2))
    end = time.time()
    print(end - start)
    > 3
    12.002995729446411
    

    相同代码,使用 lru_cache (时间就是 一次计算的时间)

    @lru_cache
    def test_lru_cache(a, b):
        time.sleep(3)
        return a+b
    start = time.time()
    test_lru_cache(1, 2)
    test_lru_cache(1, 2)
    test_lru_cache(1, 2)
    print(test_lru_cache(1, 2))
    end = time.time()
    print(end - start)
    >3
    3.001422166824341
    
    

    lru_cache 不适用于 使用 time.time() 等随机参数的函数(因为没有意义)
    被lru_cache 装饰的函数 具有 cache_info()方法,可以返回该函数缓存的 结构:
    还是上面的 test_lru_cache 函数

    for i in range(100):
        test_lru_cache(1, i)
    print(test_lru_cache(1, 130))
    print(test_lru_cache.cache_info())
    >CacheInfo(hits=0, misses=101, maxsize=128, currsize=101)
    hits -> 命中次数  (缓存的结果中,直接通过缓存拿到结果的次数,是重复计数的)
    misses -> 未命中次数 
    maxsize -> 最大容量
    currsize -> 当前容量  (<=maxsize)
    

    下面的暂时不讲,感觉不怎么用到

    • @functools.total_ordering
    • class functools.partialmethod(func, /, *args, **keywords)
    • functools.reduce(function, iterable[, initializer])
    • @functools.singledispatch
    • class functools.singledispatchmethod(func)
    • functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

    相关文章

      网友评论

          本文标题:python functools

          本文链接:https://www.haomeiwen.com/subject/eyxctktx.html