闭包和装饰器

2018-11-22  本文已影响0人  莫辜负自己的一世韶光

全局和局部变量

定义在函数内部或者作为函数的形参的变量就是局部变量.

局部变量只能在函数内部使用,首次对变量赋值的时候创建了局部变量,再次赋值的时候修改了绑定关系.
形参类型的局部变量是在调用函数的时候被常见,函数调用结束的时候销毁.

定义在函数的外部,模块内的变量称为全局变量.
模块内所有的函数都可以访问全局变量,但是在函数内部不可以直接赋值,必须通过global声明,否则会被当成是局部变量的创建.

x = 100
def f1():
    global x #说明x变量是全局变量
    x = 200
f1()
print("x = ",x)#200

y = 200
def f2():
    y = 300
    global y #这里是错误的,因为全局变量在声明之前被赋值了.

f2()
print("y = ",y)


v = 100
def f3(v):
    global v #这里是错误,形参不能声明为全局变量.
    v = 200
    #一个变量不可能同时被声明为全局变量和形参变量.

globals()和locals()函数返回的是一个字典,里面存放的是变量名和变量值的键值对.
其中globals()返回的是当前模块所有的全局变量,不仅仅局限于你自己使用的全局变量.

#globals()和locals()返回的是一个字典
a = 1
b = 2
c = 3

def fx(c,d):
    e = 300
    print("locals() 返回",locals())
    print("globals() 返回",globals())
    print(c)

if __name__ == '__main__':
    fx(4,5)

Python中的四个作用域规则(LEGB)

一个变量名的查找顺序:

本地变量 ==> 包裹着这个函数外部嵌套函数作用域 ==> 全局作用域 ==> 内置变量

nolocal语句

告诉python解释器,nolocal语句声明的变量既不是全局变量,也不是局部变量,而是外部嵌套函数内的变量.

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/11/21 10:13'

# nonlocal语句只能使用在嵌套函数的内部函数,如果没有外部函数,则nonlocal语句会报错
v = 100
def f1():
    # nonlocal v,这里会报错,no binding for nonlocal 'v' found
    v = 200

# 如果有多层,就近选择最近的层.
def f2():
    # nonlocal v 会报错,因为f2()没有外部函数
    v = 200
    def f3():
        v = 300
        def f4():
            nonlocal v # 这里声明的v是f3()里面的v
            v = 400 # 这里的赋值不会改变f2()里面的v
            print("f4()里面的v是:",v) #400
        print("f3()里面的v是:",v) # 300
        f4()
    print("f2()里面的v是:",v)  # 200
    f3()
f2()

闭包

在嵌套函数中引用了自由变量的函数.这个自由变量就是外层嵌套函数中的变量(非全局变量)

1. 必须是嵌套函数
2. 内层嵌套函数必须引用了外层嵌套函数的变量
2. 外层嵌套函数的返回值是内层嵌套函数.

嵌套函数的内层函数可以使用外层函数的变量,即使外层函数返回了,或者被删除了.内层函数依然可以使用外层函数的那个变量.

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/11/21 10:49'


# 闭包,fn使用了变量y并且外层函数make_power的返回值是fn(内层嵌套函数)
# 这个闭包可以实现x的y次方
def make_power(y):
    # 自由变量就是y,当函数make_power返回后,fn依旧可以使用y的值
    def fn(x):
        return x ** y

    return fn


# 让其实现平方的功能
f2 = make_power(2)
# 求某个数的平法
print("5的平法是: {}".format(f2(5)))

# 求立方的功能
f3 = make_power(3)

# 求某个数的立方
print("5的立方是: {}".format(5))


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

f = func()
f()

# 打印
# (<cell at 0x000001BCF3E63DC8: str object at 0x000001BCF3E2BA40>,)
# eva

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

f2 = func2()
f2()

# None
# egon

注意

定义在函数内部的嵌套函数,不能直接在全局内直接使用,所以必须要用外层嵌套函数返回.

就是一个变量,保存了函数所在的内存地址.

装饰器函数

概述

简单来说,装饰器就是一个包装函数的函数,它的本质上就是闭包函数.一般就是传入一个函数或类,在不修改原来的函数以及其调用方式的前提下为原来的函数增加新的功能.通常的做法是,在装饰器函数的内层函数中调用被装饰的函数然后在被装饰的函数调用前面或者后面加上我们要执行的代码,达到扩展其功能的目的.比较常用的场景就是日志插入,事务处理.其实装饰器函数本质上就是函数名和函数地址的重新绑定.被装饰的函数虽然看起来和原来的函数名字是一样的,但是其在内存中已经修改了绑定关系,它已经绑定到我们装饰器函数的内层函数.

装饰器首先要明白两点

1. 装饰器函数是用被装饰函数作为参数来实现的,它其实是一个闭包函数.
2. 装饰器函数是一个嵌套函数,外层函数的返回值其实就是被装饰过后的函数

1. 一个初始的函数

import time
def func():
    print("hello")
    time.sleep(1)
    print("world")

2. 如果想要记录这个函数的执行时间,最原始的做法是入侵到原来的函数中进行修改

import time
# 计算一个函数的运行的时间,最暴力的方法
def func():
    startTime = time.time()

    print("hello")
    time.sleep(1)
    print("world")

    endTime = time.time()

    seconds = endTime - startTime
    print("函数运行了{}秒".format(seconds))

if __name__ == '__main__':
    func()

3.如果不想入侵到原来的代码,可以将函数作为参数传入到一个具有计时功能的函数中

import time
#不改变原函数的代码的情况下统计函数执行所需的时间
def myfunc():
    print("hello")
    time.sleep(1)
    print("world")

#不改变原来的函数功能,但是每次都要执行该函数
def deco(func):
    startTime = time.time()
    # 运行函数
    func()
    endTime = time.time()

    seconds = endTime - startTime
    print("func()执行了{}秒".format(seconds))

if __name__ == '__main__':
    f = myfunc;
    #这里要统计时间的时候,每次都要调用deco()函数
    deco(f)

这样有一个问题就是每次都要调用deco()函数来实现计时

4. 使用装饰器,既不改变原来函数的功能,又不需要重复调用deco()函数

# 既不需要入侵原函数,也不用重复调用
import time
def deco(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        print("程序执行了{}秒".format(endTime - startTime))

    return wrapper
    
#声明myfunc()是一个装饰器函数
@deco
def myfunc():
    print("Hello!")
    time.sleep(1)
    print("World!")

if __name__ == '__main__':
    f = myfunc #将myfunc赋值给f变量
    f();

这里的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。其中作为参数的这个函数func()就在返回函数wrapper()的内部执行。然后在函数func()前面加上@deco,func()函数就相当于被注入了计时功能,现在只要调用func(),它就已经变身为“新的功能更多”的函数了。所以这里装饰器就像一个注入符号:有了它,拓展了原来函数的功能既不需要侵入函数内更改代码,也不需要重复执行原函数。

编程的开放封闭原则

1. 扩展开放
为什么要对扩展开放呢?
    我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
2. 修改关闭
为什么要对修改封闭呢?

就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。

装饰器完美的遵循了这个开放封闭原则。

装饰器的固定格式(接收任意的参数)


def timer(func):
    def inner(*args,**kwargs):
        '''执行函数之前要做的'''
        re = func(*args,**kwargs)
        '''执行函数之后要做的'''
        return re
    return inner
from functools import wraps

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

带参数的装饰器

# encoding:utf-8
import time

__author__ = 'Fioman'
__date__ = '2018/11/21 14:03'


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

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

func1(2)

这还不是难的,如果装饰的函数不一样,参数也不同,如何写呢

# encoding:utf-8
import time

__author__ = 'Fioman'
__date__ = '2018/11/21 14:06'

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 "func2 over"

func1('aaaaa','bbbbb')
print(func2('aaaaaa'))

刚刚的装饰器已经很完美了,但是我们正常查看函数的一些信息的方法会失效.

def index():
    '''这是主页信息'''
    print('from index')
print(index.__doc__) # 查看函数说明说明文档的方法
print(index.__name__) # 查看函数名的方法

解决这种小不足的方法,是在内嵌函数加上wraps(被装饰的函数)

# encoding:utf-8
from functools import wraps

__author__ = 'Fioman'
__date__ = '2018/11/21 14:13'


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__)

总结

而functools.wraps 则可以将被装饰的函数的指定属性复制给装饰函数对象, 默认有 modulenamedoc,或者通过参数选择

多个装饰器修饰一个函数

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/11/21 14:26'

def wrapper1(func):
    def inner():
        print('装饰器1里,func被调用之前')
        func()
        print('装饰器1里,func被调用之后')
    return inner


def wrapper2(func):
    def inner():
        print("装饰器2里,func被调用之前")
        func()
        print("装饰器2里,func被调用之后")

    return inner

@wrapper2
@wrapper1
def f():
    print('被装饰函数func被调用')

# 这里相当于是 wrapper2(wrapper1(f))   ==> wrapper2(wrapper1.inner())
# ==> 所以先走的是wrapper2.inner() ==> print2 -> 然后调用wrapper1.inner ->
# print1 --> func --> print1 ---> 最后才是调用print2

f()

打印结果

image.png

带有标志位的装饰器,可以方便的使用和取消装饰器,可以立个flag

# encoding:utf-8
__author__ = 'Fioman'
__date__ = '2018/11/21 14:35'

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(1111)

func()

上一篇 下一篇

猜你喜欢

热点阅读