zoukankan      html  css  js  c++  java
  • python教程(三)·函数进阶(下)

    下半部分果然很快到来,这次介绍函数的更高级用法,装饰器!

    函数嵌套

    先来说说函数嵌套,python中的函数是可以嵌套的,也就是说可以将一个函数放在另一个函数里面,比如:

    >>> def outer(name):
    ...     def inner():
    ...             print(name)
    ...     return inner
    ... 
    >>> func1 = outer('feather')
    >>> func2 = outer('Lee')
    >>> func1()
    feather
    >>> func2()
    Lee
    >>> 
    

    你没看错,我们在函数里面定义了另一个函数,并把这个函数返回了?

    返回出来的函数是带着它的所在的作用域的,这就是为什么返回出来的函数仍能访问外层函数的变量name,而且可以看到func1()func2()结果不一样,这表明每次调用外层函数都会重新定义内层函数,事实也是如此。

    装饰器

    再来看看下面这个函数,<( ̄︶ ̄)↗

    def func():
        for i in range(100000):
            pass  # pass的意思是什么都不做
    

    这段函数只是空转100000次没什么意义,只是做个例子,假如我们想要运使用这个函数并输出运行了多长时间,我们可以这么做:

    import time
    
    def func():
        for i in range(100000):
            pass  # pass的意思是什么都不做
    
    # 获取当前时间戳,时间戳就是当前到1970年所经过的秒数
    start = time.time()  
    func()
    end = time.time()
    
    print(end-start)
    

    我们可以在函数前后分别获取时间,两个时间的差就是函数的运行时间,这很简单。可是,如果我们有很多个函数都需要统计时间怎么办?每个函数调用前都加上同样的代码吗?

    当然不是,受函数嵌套的启发,我们可以像下面这样定义一个函数:

    def count(func):
        def inner():
            start = time.time()
            result = func()
            end = time.time()
            print(end-start)
            return result
        return inner()
    

    然后把要计算运行时间的函数作为参数传给count函数,就是把count函数当一个代理人,替我们调用函数。

    但是这样的话,调用func函数不能写func(),要写count(func),有点别扭,再改进下:

    def count(func):
        def inner():
            start = time.time()
            result = func()
            end = time.time()
            print(end-start)
            return result
        return inner    # 返回这个内层函数,而不是调用
    

    如果再执行了这样的代码:

    func = count(func)
    

    这个什么意思?

    其实就是把func这个变量(函数也可理解成一种变量)重新绑定成count里面定义的inner()函数。这个时候我们再执行func()就可以达到效果了,完整代码如下:

    import time
    
    def func():
        for i in range(100000):
            pass  # pass的意思是什么都不做
    
    def count(func):
        def inner():
            # 获取当前时间戳,时间戳就是当前到1970年所经过的秒数
            start = time.time()  
            result = func()
            end = time.time()
            print(end-start)
            return result
        return inner
    
    func = count(func)
    func()
    

    装饰器的雏形出来了!装饰器就是像count这样的函数,用来给其它函数增加额外的功能,count函数在里面定义了一个inner函数用来包裹(装饰)被装饰的函数func。

    不过这仅仅是雏形,并不是真正的装饰器,现在该让真正的装饰器登场了!

    import time
    
    def count(func):
        def inner():
            # 获取当前时间戳,时间戳就是当前到1970年所经过的秒数
            start = time.time()  
            result = func()
            end = time.time()
            print(end-start)
            return result
        return inner
        
    @count
    def func():
        for i in range(100000):
            pass  # pass的意思是什么都不做
            
    func()
    

    在函数func()的上面多了样东西:@count,这个东西就是装饰器,它的本质等同于func = count(func)。这样以后,每当我们需要为哪个函数的增加输出运行时间的功能,就在这个函数的定义前加上@count

    万能的***

    如果func函数的参数数量不确定,我们可以使用参数收集和分配参数的技巧,代码如下:

    def count(func):
        # “百搭”的参数定义
        def inner(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)  # 展开参数
            end = time.time()
            print(end-start)
            return result
        return inner
    

    这样的count装饰器对于参数数量任意的函数都可以匹配了。

    带参数的装饰器

    装饰器也可以有参数!

    import time
    
    def count(switch):
        # switch为True则计时,否则关闭计时功能
        def wrapper(func):
            def inner(*args, **kwargs):
                if switch:
                    start = time.time()  
                    result = func()
                    end = time.time()
                    print(end-start)
                    return result
                else:
                    return func()
            return inner
        return wrapper
    
    switch = bool(input('是否计时?(y/n)') == 'y')
    
    @count(switch)
    def func():
        for i in range(100000):
            pass  # pass的意思是什么都不做
    
    func()
    

    看完以后是不是有点晕?(@_@;)

    我来说说这个运行的过程:首先,程序等待输入,假定输入y,这是switch变量赋值为True,然后遇到@count(switch),这一行相当于调用了count函数,这个函数返回了一个装饰器,就是里面的wrapper,然后对func函数使用这个装饰器。

    (°ー°〃)

    嗯?我应该说清楚了吧。。。

    对!还有一个问题 (°ー°〃)

    函数签名

    每个函数都有一个属性,叫做函数签名,就是函数的名字,比如print函数的函数签名可以这样获得:

    >>> print.__name__
    'print'
    >>> 
    

    一个函数使用了装饰器,表面上还是使用这个函数的名字,实际上真的是这样吗?看下面代码:

    >>> def outer(func):
    ...     def inner():
    ...             pass
    ...     return inner
    ... 
    >>> @outer
    ... def func():
    ...     pass
    ... 
    >>> func.__name__
    'inner'
    >>> 
    

    函数的签名已经改变了!

    可是...

    这个能有什么问题?

    ( ̄︶ ̄)↗ 有些时候我们是需要使用这个函数签名的的,比如:根据输入的函数名字使用对应的函数(根据字符串获取函数的方法在后面的小实例有用到),有些模块也可能使用到了函数签名,如果不注意到这个细节,出错了也找不出什么问题来!

    那么怎么解决?

    解决这个问题的方法很简单,我们只需使用一个内置模块的装饰器:

    >>> import functools
    >>> def outer(func):
    ...     @functools.wraps(func)
    ...     def inner():
    ...             pass
    ...     return inner
    ... 
    >>> @outer
    ... def func():
    ...     pass
    ... 
    >>> func.__name__
    'func'
    >>> 
    

    代码简洁明了,至于原理,我们奉行 “拿来主义”,只需要知道在用于包裹的函数上方加上@functools.wraps()即可,不必纠结functools.wraps的内部实现

    多个装饰器

    装饰器可以使用多个,多个装饰器的执行顺序是从下往上的,看下面这个例子:

    >>> def wrapper_1(func):
    ...     def inner():
    ...             pass
    ...     print('wrapper_1')
    ...     return inner
    ... 
    >>> def wrapper_2(func):
    ...     def inner():
    ...             pass
    ...     print('wrapper_2')
    ...     return inner
    ... 
    >>> @wrapper_1
    ... @wrapper_2
    ... def func(): 
    ...     pass
    ... 
    wrapper_2
    wrapper_1
    >>>
    

    这很好理解,我们在包装东西的时候,都是先从最里面的一层开始,一层一层包裹,直到最外层(就像“俄罗斯套娃“),所以先执行@wrapper_2再执行外面的@wrapper_1

    与此同时,这个例子中并没有调用func函数,但是却有执行的print语句,这更进一步证明了,@wrapper这样的语句相当于执行了func=wrapper(func),这是在函数定义的时候马上就执行的,而不是调用的时候才执行的,要注意了


    到目前,我们对函数的讲解就此结束,内容很多,读者们有得是时间消化了,也为我准备后面小实例提供了准备时间,读者们期待吧!(提示一下,我们将使用微信控制电脑!)

    ヾ( ̄▽ ̄)Bye~Bye~

  • 相关阅读:
    EF Core1.0 CodeFirst为Modell设置默认值!
    MvcPager分页控件使用注意事项!
    一个关于A标签和分页的怪问题!
    让Visual Studio Code对jQuery支持智能提示!
    MVC中获取所有按钮,并绑定事件!
    EF6.0 Code First使用mysql的各种错误和解决办法!!
    记住 MVC里用formcollection接收form表单传来的值,表单属性必须有name为健!
    Hibernate处理oracle lob总结
    怎样写 OpenStack Neutron 的 Extension (一)
    怎样写 OpenStack Neutron 的 Extension (二)
  • 原文地址:https://www.cnblogs.com/featherl/p/10344340.html
Copyright © 2011-2022 走看看