day 11 闭包 + 闭包嵌套 + 装饰器

闭包:

  闭包的作用:内部函数包括对外部作用于而非全局作用域的引用。该内部函数被称为闭包函数。

    函数内部定义的函数被称为内部函数,

 

  由于有了作用域的关系,我们就不能拿到函数内部的变量和函数了。如果我们就是想拿怎么办呢?返回呀!

  我们都知道函数内的变量我们要想在函数外部用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数呢?

  是不是直接就把这个函数的名字返回就好了?

  这才是闭包函数最常用的用法

 

def func():
    name = 'eva'
    def inner():
        print(name)
    return inner

f = func()
f()

判断闭包函数的方法__closure__

#输出的__closure__有cell元素 :是闭包函数
def func():
    name = 'eva'
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f = func()
f()

#输出的__closure__为None :不是闭包函数
name = 'egon'
def func2():
    def inner():
        print(name)
    print(inner.__closure__)
    return inner

f2 = func2()
f2()

多个装饰器装饰一个函数(重点)

六,多个装饰器装饰一个函数。
 

def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args,**kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re
        return inner
    return timer

@outer(False)
def func():
    print(111)

func()
复制代码
 

返回顶部
多个装饰器装饰同一个函数

有些时候,我们也会用到多个装饰器装饰同一个函数的情况。


复制代码
def wrapper1(func):
    def inner():
        print('wrapper1 ,before func')
        func()
        print('wrapper1 ,after func')
    return inner

def wrapper2(func):
    def inner():
        print('wrapper2 ,before func')
        func()
        print('wrapper2 ,after func')
    return inner

@wrapper2
@wrapper1
def f():
    print('in f')

f()

 

 

 

 

 

 

闭包的嵌套:

def func():
    name = '老男孩'
    def guess():
        play = 'ball'
        def inner():
            return name + play
        return inner
    return guess

ret = func()
x = ret()
print(x())

闭包函数获取网络应用:

from urllib.request import urlopen
def func():
    url = 'https://www.taobao.com/'

    def inner():
        return urlopen(url).read()
    return inner
ret = func()
print(ret())

 

 

装饰器:

  什么是装饰器?

    ——装饰器本质上就是一个python函数,它可以让其他函数再不做任何代码变动的情况下,增加额外的功能

    装饰器的返回值也是一个函数

装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景

二,装饰器的形成过程。

现在我有一个需求,我想让你测试这个函数的执行时间,在不改变这个函数代码的情况下:

复制代码
import time

def func1():
    print('in func1')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

func1 = timer(func1)
func1()
复制代码

但是如果有多个函数,我都想让你测试他们的执行时间,你每次是不是都得func1 = timer(func1)?这样还是有点麻烦,因为这些函数的函数名可能是不相同,有func1,func2,graph,等等,所以更简单的方法,python给你提供了,那就是语法糖。

复制代码
import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> func1 = timer(func1)
def func1():
    print('in func1')


func1()
复制代码

 刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?

复制代码
def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner

@timer
def func1(a):
    print(a)

func1(1)

装饰器——带参数的装饰器
复制代码
复制代码
import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        re = func(*args,**kwargs)
        print(time.time() - start)
        return re
    return inner

@timer   #==> func1 = timer(func1)
def func1(a,b):
    print('in func1')

@timer   #==> func2 = timer(func2)
def func2(a):
    print('in func2 and get a:%s'%(a))
    return 'fun2 over'

func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))
复制代码

上面的装饰器已经非常完美了,但是有我们正常情况下查看函数信息的方法在此处都会失效:

复制代码
def index():
    '''这是一个主页信息'''
    print('from index')

print(index.__doc__)    #查看函数注释的方法
print(index.__name__)   #查看函数名的方法
复制代码

如何解决呢?

复制代码声明:从functools中导入wraps方法:
  因为:在打印函数名的时候,如果不导入wraps方法,打印的函数名(print(被装饰函数名.__name__))不是原函数名而是内部函数名。
 
from functools import wraps

def deco(func):
    @wraps(func) #加在最内层函数正上方
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

@deco
def index():
    '''哈哈哈哈'''
    print('from index')

print(index.__doc__)
print(index.__name__)
复制代码

 装饰器的叠加:

 

叠放装饰器
执行顺序是什么
如果一个函数被多个装饰器修饰,其实应该是该函数先被最里面的装饰器修饰后(下面例子中函数main()先被inner装饰,变成新的函数),变成另一个函数后,再次被装饰器修饰

def outer(func):
    print('enter outer', func)
    def wrapper():
        print('running outer')
        func()
    return wrapper

def inner(func):
    print('enter inner', func)
    def wrapper():
        print('running inner')
        func()
    return wrapper

@outer
@inner
def main():
    print('running main')

if __name__ == '__main__':
    main()

#返回结果
# enter inner <function main at 0x000001A9F2BCDF28>
# enter outer <function inner.<locals>.wrapper at 0x000001A9F2BD5048>
# running outer
# running inner
# running main

类装饰器
相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

import time

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        start_time=time.time()
        self._func()
        end_time=time.time()
        print('spend %s'%(end_time-start_time))

@Foo  #bar=Foo(bar)
def bar():
    print ('bar')
    time.sleep(2)
    
bar()    #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法

标准库中有多种装饰器
例如:装饰方法的函数有property, classmethod, staticmethod; functools模块中的lru_cache, singledispatch,  wraps 等等
from functools import lru_cache
from functools import singledispatch
from functools import wraps
 
functools.wraps使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

def foo():
    print("hello foo")
print(foo.__name__)# foo

def logged(func):
    def wrapper(*args, **kwargs):
        print (func.__name__ + " was called")
        return func(*args, **kwargs)
    return wrapper

@logged
def cal(x):
    resul=x + x * x
    print(resul)

cal(2)
#6
#cal was called
print(cal.__name__)# wrapper
print(cal.__doc__)#None
#函数f被wrapper取代了,当然它的docstring,__name__就是变成了wrapper函数的信息了。

好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

from functools import wraps

def logged(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return wrapper

@logged
def cal(x):
    return x + x * x

print(cal.__name__)  # cal

使用装饰器会产生我们可能不希望出现的副作用, 例如:改变被修饰函数名称,对于调试器或者对象序列化器等需要使用内省机制的那些工具,可能会无法正常运行;
其实调用装饰器后,会将同一个作用域中原来函数同名的那个变量(例如下面的func_1),重新赋值为装饰器返回的对象;使用@wraps后,会把与内部函数(被修饰函数,例如下面的func_1)相关的重要元数据全部复制到外围函数(例如下面的decorate_inner)

from functools import wraps

def decorate(func):
    print('running decorate', func)
    @wraps(func)
    def decorate_inner():
        print('running decorate_inner function', decorate_inner)
        return func()
    return decorate_inner

@decorate
def func_1():
    print('running func_1', func_1)

if __name__ == '__main__':
    func_1()

#输出结果
#running decorate <function func_1 at 0x0000023E8DBD78C8>
# running decorate_inner function <function func_1 at 0x0000023E8DBD7950>
# running func_1 <function func_1 at 0x0000023E8DBD7950>

 

posted @ 2018-03-31 20:56  Coca-Mirinda  阅读(143)  评论(0)    收藏  举报