zoukankan      html  css  js  c++  java
  • Python学习笔记11:函数修饰符

    Python学习笔记11:函数修饰符

    Python有很多有趣的特性,其中函数修饰符就是一个。

    我们在之前的那个web应用示例中用过如下写法:

    @web.route('/log')
    

    @符号后边的,就是一个函数修饰符,它可以在不改变原有函数的情况下改变函数的行为(通常来说是增强函数的行为)。

    我们下面就来说说怎样实现一个函数修饰符。

    在这之前,我们要先介绍几个必须要先掌握的内容。

    接收和返回函数

    如我们在Python学习笔记0:变量中讨论的,在Python中,所有的一切都是对象,而函数也不例外。那理所应当的,函数也可以作为对象被另一个函数所接收和返回。

    我们看下边的例子:

    def receiveFunc(func):
        print(type(func))
        return func
    
    def hellow():
        print("hellow world")
    
    returned=receiveFunc(hellow)
    returned()
    

    输出

    <class 'function'>
    hellow world

    我们可以看到,receiveFunc接收了函数hellow并打印出其类型,然后返回,返回的函数依然可以正常执行并输出结果。

    可变参数列表

    我们都知道,在函数签名的定义中,参数数目可以是一个,也可以是多个,但都是定义的时候就指定的,但Python还有另一种定义方法,可以接收数目不定的参数。

    def getMultipParam(*multipParams):
        for param in multipParams:
            print(param,' ',end='')
        print()
        
    getMultipParam(1,2,3)
    getMultipParam('a','b','c','d','e')
    

    输出

    1 2 3
    a b c d e

    通过类似*params这种方式,可以指定参数接收的是可变长度的参数列表,这种方式有点像C++中的指针,可以接收一个数组作为参数,而在Python这里,就是接收一个列表,本质上接收函数会把收到的参数列表转变为一个列表进行处理。

    当然,你也可以直接传一个列表过去,但是需要用*来指定:

    def getMultipParam(*multipParams):
        for param in multipParams:
            print(param,' ',end='')
        print()
        
    getMultipParam(*[1,2,3])
    getMultipParam(*['a','b','c','d','e'])
    

    输出

    1 2 3
    a b c d e

    可以看出两者是等价的。但是这么做是有意义的,其目的在于要用*来说明传过去的列表是作为可变参数列表来传递,而非是仅仅一个普通参数。当然,如果不这么做接收函数就会认为仅仅传入了一个参数,是个列表。

    到这里,关于可变参数的内容说完了吗?并没有。

    如果大家还记得,参数传递还有另一种方式,即指明参数名称,进行一对一的传递。如果是那种方式,可变参数要如何定义呢?

    def getKVParams(**kvParams:dict):
        for paramKey,paramVal in kvParams.items():
            print(paramKey,':',paramVal,end=',')
        print()
        
    getKVParams(name="jack",age=16)
    getKVParams(career="enginner",age=18)
    

    输出

    name : jack,age : 16,
    career : enginner,age : 18,

    和之前的定义类似,不过是将传入参数作为字典来处理,相应的,也同样可以直接把可变参数列表作为字典来传入:

    def getKVParams(**kvParams:dict):
        for paramKey,paramVal in kvParams.items():
            print(paramKey,':',paramVal,end=',')
        print()
        
    getKVParams(**{"name":"jack","age":16})
    getKVParams(**{"career":"teacher","age":17})
    

    输出

    name : jack,age : 16,
    career : teacher,age : 17,

    你以为到这里就结束了?

    太天真,我们还可以把这两者结合起来!

    def getEveryParams(*multipParams,**kvParams:dict):
        for param in multipParams:
            print(param,end=',')
        print()
        for paramKey,paramVal in kvParams.items():
            print(paramKey,':',paramVal,end=',')
        print()
        
    getEveryParams(1,2,3,age=16,name="jack")
    

    输出:

    1,2,3,
    age : 16,name : jack,

    可以看到,通过将两种方式结合,我们的函数就可以接收任何形式的传参了,至于这有什么用,不用急,看完这篇博客就能明白了。

    关于前置知识,还有最后一个,内部函数。

    内部函数

    相信对Java不陌生的朋友应该会在此时说,这个我会,内部类!

    不错,Python的内部函数在我看来和Java的内部类颇为类似,而且事实上两者也是一致的,因为我一直所强调的,在Python里,一切都是对象,所以Python会存在和Java内部类高度类似的东西也不足稀奇。

    def getInnerFunc():
        def innerFunc():
            print("hellow world")
        return innerFunc
    
    test=getInnerFunc()
    test()
    

    输出

    hellow world

    可以看到,getInnerFunc函数的内部定义了一个内部函数,然后将这个函数返回,而外部程序拿到返回的函数以后也可以正常执行。而这种使用方式也恰恰是内部函数的最常用方式。

    函数修饰符

    好了,终于要讲到我们的主题了,如何构造一个我们自己的函数修饰符。

    其实其核心要点就是把我们之前讲的三种技巧结合起来。

    我们始终要有这样的概念,函数修饰符的作用是将一个已有的函数增强其用途。

    这种思想可以用现实的例子来类比,就拿我喜欢的军事来说吧。现在的主战坦克在真正作战中肯定不会裸奔,需要按作战行为增加诸多附加模块,比如悬挂附加装甲,增加自动反导弹装置,还有眼目发生器和红外干扰装置之类的更是稀松平常。

    但是这些最重要的是什么,是我们在没有改变坦克本身的情况下增强了坦克的作战性能。

    是不是很酷?

    我们现在就在Python中这么做吧。

    我们先定义一个坦克:

    def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
        print("这是一个裸奔的坦克")
        print("该坦克有四名成员")
    

    别问为什么这里没有用英语。。。

    我们现在用坦克工厂给它加装反应装甲:

    def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
        print("这是一个裸奔的坦克")
        print("该坦克有四名成员")
    
    def tankFactory(tank:function)->function:
        def powerTank():
            tank()
            print("加装反应装甲")
            print("加装反导弹装置")
            print("加装红外干扰仪")
        return powerTank
    

    可以看到,我们利用上边说的两个特性来构建了一个坦克工厂,来增强进入工厂的坦克。

    等等,我们是不是忘掉了什么,对,我们这里忘记了使用可变参数,我们需要注意到,上边的示例是有问题的,比如从工厂返回的powerTank居然没有参数,这就相当于这个增强版坦克顶盖被焊死了,不能进驾驶员,这显然是不合理的,没有部队会接受这样的改装。

    那我们像tank的定义一样指定四个成员作为参数行不行?当然是可以的,但是这同样有问题,比如现在一些先进坦克是自动装填,不需要炮手,只要三个成员,那你这工厂就不能接受这种坦克了,这相当有局限性。

    所以,这里就是我们的可以接受任何类型参数的技巧的作用所在了,让我们改进一下我们的坦克工厂:

    def tankFactory(tank:"function")->"function":
        def powerTank(*params:tuple,**kvParams:dict):
            tank(*params,**kvParams)
            print("已加装反应装甲")
            print("已加装反导弹装置")
            print("已加装红外干扰仪")
        return powerTank
    
    @tankFactory
    def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
        print("这是一个裸奔的坦克")
        print("该坦克有四名成员")
    
    @tankFactory
    def mordenTank(player1,player2,player3):
        print("这是一个3成员的现代坦克")
    
    tank("tom","jerry","robot1","robot2")
    mordenTank("tom","jerry","robot")
    

    输出:

    这是一个裸奔的坦克
    该坦克有四名成员
    已加装反应装甲
    已加装反导弹装置
    已加装红外干扰仪
    这是一个3成员的现代坦克
    已加装反应装甲
    已加装反导弹装置
    已加装红外干扰仪

    可以看到,我们现在的坦克工厂非常棒,通过在要改装的坦克函数前简单的加上@tankFactory就可以直接改装。真是太方便了。

    等等,在你准备撸袖子改装掉所有类型的坦克前,还需要注意一点,因为Python中所有函数都是对象,而作为函数修饰符的函数也不例外,而解释器在处理这种特殊的函数时,有时候会忘记这是一个函数修饰符。所以我们需要显示地告诉Python解释器,这是一个作为函数修饰符的特殊函数,而非普通货色。

    当然,这有点奇怪,但我们要做的是接受它。

    至于如何做,很简单:

    functools模块引入一个函数wraps,并在函数修饰符中调用这个函数。

    from functools import wraps
    def tankFactory(tank:"function")->"function":
        @wraps(tank)
        def powerTank(*params:tuple,**kvParams:dict):
            tank(*params,**kvParams)
            print("已加装反应装甲")
            print("已加装反导弹装置")
            print("已加装红外干扰仪")
        return powerTank
    
    @tankFactory
    def tank(player1:'驾驶员',player2:'装填手',player3:'车长',player4:'炮长'):
        print("这是一个裸奔的坦克")
        print("该坦克有四名成员")
    
    @tankFactory
    def mordenTank(player1,player2,player3):
        print("这是一个3成员的现代坦克")
    
    tank("tom","jerry","robot1","robot2")
    mordenTank("tom","jerry","robot")
    

    这就是最终的代码了。

    关于函数修饰符的内容已经讲完了,但其实还有一些扩展性的问题可以讨论,比如函数修饰符这种叫法很怪异和别扭,其实叫做修饰器更妥当,因为其本质就是设计模式中的修饰器模式。还有函数修饰符和我们前边讲的上下文模式也颇为类似,是不是可以用函数修饰符实现一个上下文模式?

    诸如此类。

    但是因为今天忙了别的,已经有点晚了,就将这些内容和下一篇笔记一起发吧。

    晚安。

    本篇文章首发自魔芋红茶的博客https://www.cnblogs.com/Moon-Face/ 请尊重其他人的劳动成功,转载请注明。
  • 相关阅读:
    git(重点)
    C#技巧记录——持续更新
    结点和节点的区别
    WebSocketSharp send record_stop without send record_start
    cefsharp 拦截所有请求 解决chunked导致数据接收不完整的问题
    计算mp3长度 毫秒
    pydub分割音频文件
    c# 获取文件信息
    实现一边写代码一边展示网页的效果
    c# webapi swagger Area 多级层次分组 添加header参数
  • 原文地址:https://www.cnblogs.com/Moon-Face/p/14582298.html
Copyright © 2011-2022 走看看