zoukankan      html  css  js  c++  java
  • (转)python装饰器进阶一

    Python装饰器进阶之一

    先看例子

    网上有很多装饰器的文章,上来说半天也没让人看明白装饰器到底是个什么,究竟有什么用,我们直接来看几个例子。

    Python递归求斐波那契数列

    def fibonacci(n):
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    print(fibonacci(50))

    这是一个很常见的求斐波那契数列的例子,但是这其中有非常多的重复计算。

    如:我们要计算第10位数字是多少,就要先计算出第9位数字是多少,并且是从0计算到9。此时我们已经计算过0-9位数字的值,甚至已经知道第10位数字的值,但是当计算第11位的时候却又要重复一遍。也就是说我们如果计算第50位数字是多少,就要先计算0-1,0-2,0-3 ... 0-48,0-49,0-50。数字越大,重复的计算就越多。上面的程序有兴趣的可以运行一下,我等待了五分钟没有出结果就强行在任务管理器关闭了。

    略微修改一下

    # 增加一个缓存字典
    def fibonacci(n, cache=None):
        # 第一次没有任何缓存,初始化字典
        if cache is None:
            cache = {}
        # 如果n这个数字,是我们已经计算过的,就不需要再去递归从头计算了
        if n in cache:
            return cache[n]
    
        if n <= 1:
            return 1
        # 如果缓存没有,就把它增加到缓存中去
        cache[n] = fibonacci(n - 1, cache) + fibonacci(n - 2, cache)
        return cache[n]
    
    print(fibonacci(50))

    运行结果:

    20365011074
    [Finished in 0.1s]

    前面运行五分钟没有任何结果,而简单修改以后仅仅用了0.1秒,可见重复计算对性能的影响用几何倍数称呼都不为过。

    思考:

    这种重复计算的问题,在我们的项目中是非常常见的,特别是一些计算概率的方法,都需要重复计算。那么我们总不能每个方法都这样改写一次吧?那样我们的项目中会包含大量的冗余代码,不利于我们的维护,也违背了我们高内聚,低耦合的编程思想。那我们能否实现,只用写一个增加缓存的方法,然后其他的方法我们都使用这个方法来装饰它,让它拥有这个功能。

    实现思路:

    1. 写一个方法假设方法名为add_cache,该方法拥有缓存
    2. add_cache接收一个参数,参数就是原方法的对象
    3. 在add_cache方法内部,有一个wrap方法,该方法就接收原来fibonacci方法的参数
    4. wrap方法首先判断参数是否在缓存中,如果在,就直接返回缓存中的值
    5. 如果不在缓存中就调用原来的fibonacci方法计算然后把值保存在缓存中
    6. 在add_cache中返回新的wrap方法的对象

    举个栗子

    # 装饰方法
    def add_cache(func):
        cache = {}
        # 因为方法可能不是都只有一个参数,所以我们使用这种形式可以接收任意个参数
        # wrap可以随便起名的,还有add_cache都是随便起名字不是固定的
        def wrap(*args):
            # 如果这些参数不在cache中
            if args not in cache:
                # 调用原方法,并且把这些参数对应的值存储在cache中
                # 需要注意的是,传递给原方法我们要把元组形式的args拆分成单个参数
                cache[args] = func(*args)
            # 在cache中就直接返回
            return cache[args]
        # 我们需要把新的方法对象返回回去,这样用户调用add_cache传递旧的方法就能得到新的方法对象
        return wrap
    
    # 计算斐波那契的方法
    def fibonacci(n):
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    """
        调用装饰方法,获得新的增加了功能的对象,通常新方法名保持和原方法名一致,这样原来的业务逻辑不需要做任何修改
    """
    fibonacci = add_cache(fibonacci)
    # 需要注意的是,Python的数值也是有长度限制的,在Win10x64计算机上,最高可以计算第332位
    print(fibonacci(50))

    看完上面的例子以后,有人可能会说:这不是忽悠人呢?你以为我是新手啊,不知道什么是装饰器吗?装饰器我看人家都是用@符号后面跟个名字,然后写在方法上就可以了。

    我想说,之所以能够实现@符号装饰,是因为Python给我们提供了这样的便利,再来看上面的例子:

    def add_cache(func):
        cache = {}
        def wrap(*args):
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        return wrap
    
    # 装饰fibonacci方法,只用一个@符号,后跟自己写的装饰方法即可
    @add_cache
    def fibonacci(n):
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    # 此时这个fibonacci实际上已经被增加了新的功能
    print(fibonacci(50))

    同样能够实现之前的功能。

    提醒:我之所以不厌其烦的复制重复代码,并且一行一行删除掉已经写过的注释,就是希望能加深大家的印象,能把别处学来的东西,真正理解,应用到工作中,变成自己的东西。

    至此,我相信大家已经明白什么是装饰器了。所谓装饰器,就是给我们原来非常单调,没有某些功能的方法,增加一些功能,就跟我们玩游戏给人物带装备一样。这么想是不是非常容易理解了。

    代码我在Python2.x和3.x都测试过没问题

    废话不想说那么多,希望大家好好学习,工资蹭蹭往上涨。

    by. 秋名山车神

    END


    作者: 秋名山车神 
    链接:http://www.imooc.com/article/16227
    来源:慕课网

  • 相关阅读:
    索引信息统计
    删除脚本
    归档脚本
    SQL2005四个排名函数(row_number、rank、dense_rank和ntile)的比较
    用js输出同样字符出现的次数
    grid布局
    统计字符出现的次数
    百度地图
    For each...in,For...in ,For...of的Examples和Explanation
    Object的起源
  • 原文地址:https://www.cnblogs.com/lgh344902118/p/6835513.html
Copyright © 2011-2022 走看看