闭包和装饰器

全局和局部变量

  • 什么是局部变量

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

  • 使用注意事项

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

  • 什么是全局变量

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

  • 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()和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)

  • Local(局部作用域) L 又叫本地作用域
  • Enclosing(外部嵌套函数作用域) E
  • Global(全局作用域) G
  • Builtin(python内置模块作用域) B

一个变量名的查找顺序:

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

nolocal语句

  • 作用:

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

  • nolocal使用场景
    1. nolocal只能在被嵌套的函数内部使用
    2. 访问nolocal变量将对外部嵌套函数作用域内的变量进行操作
    3. 当有两层以上的函数嵌套时,访问nonlocal变量只对最近一层的变量进行操作
    4.形参列表中的变量,不可以出现在nonlocal声明的变量列表中,形参不可以当成是外部嵌套函数作用域变量
# 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,若果是None就不是闭包,如果有cell元素就是闭包

# 判断闭包函数额方法__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()

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容

  • python之理解闭包和装饰器 1、闭包函数 1.1 python中函数都是对象 结果: 上面定义一个shut函数...
    道无虚阅读 637评论 2 0
  • 闭包 之前一直不明白闭包的定义-内层函数引用了外层函数的变量(包括它的参数)就构成了闭包。我觉得也没什么了不起的,...
    MononokeHime阅读 267评论 0 0
  • 一、python函数作用域LEGB python解释器查找变量的原则(顺序):L→E→G→BL:Local函数内部...
    风萧雨霖阅读 444评论 0 0
  • 实际开发过程中,有可能会用到顺序加载异步的需求,比如先掉A接口,直到A接口调用完成,在调用B接口,以此类推C接口....
    Nulll阅读 1,469评论 0 0
  • 浅浅荷塘几时忧 并蒂双莲各自愁 朦胧数尺花开处 断桥荒园影浊酒
    相思无门阅读 194评论 0 2