Python闭包与装饰器

1. 闭包

概念:在函数嵌套的前提下,内层函数引用了外层函数的变量(包括参数),外层函数又把内层函数当做返回值进行返回。这个内层函数+多引用的外层变量,称为‘闭包’。
实例1:

def test1(a):
  b=10
  def test2()
    print(a)
    print(b)
  return test2

应用场景:外层函数,根据不同的参数生成不同作用功能的函数。
注意事项:

  1. 闭包中,如果要修改引用的外层变量,则需要使用nonlocal变量声明,否则会当做是闭包内,新定义的变量。
  2. 当闭包内,引用了一个后期会发生变化的变量时,一定要注意。
    实例2:
def test()
  funcs = []
  for i in range(1, 4):
    def test2():
      print(i)
    funcs.append(test2)
  return funcs
newfuncs = test()

newfuncs[0]()
newfuncs[1]()
newfuncs[2]()

>>>3
>>>3
>>>3

以上情况的出现是因为,虽然在最后newfuncs中调用不同的test2函数,但在此之前funcs函数内的test2没有被执行,而test2函数已经被定义了3次了,其中range(1, 4)也已经走了3次了,相应的i也已经从1走到3了,所以在最后调用test2的,会去取i的值,此时i=3,故三次调用都会返回3。
案例2改进:

def test():
  funcs = []
  for i in range(1, 4):
    def test2(num):
      def inner():
        print(num)
      return inner
    funcs.append(test2=(i))
  return funcs
newfuncs = test()

newfuncs[0]()
newfuncs[1]()
newfuncs[2]()

>>>1
>>>2
>>>3

解释:在改进方案中,其实就是给之前的test2内再嵌套了一个函数inner,inner负责之前test2的功能,然后每次test2加入funcs列表的时,直接把的i传入test2,同时又不执行inner函数,之后newfuncs调用test2时,其中已经包含的每次不同的i的值,inner函数执行输出的结果也就会不同。

2. 装饰器

2.1 概念理解

装饰器本质上是一个函数,它可以让其他函数在不需要做任何改动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。
装饰器经常用于有切面需求的场景:插入日志、性能测试、事务处理、缓存、权限校验等场景。
分步理解装饰器:
(1). 需要给一个函数a()添加一个功能,功能写在decorator()内,当我们调用'decrotor(a)`的时候就会在执行a()之前执行我们要的功能。

def a():
  print('i am a')

def decorator(a):
  print('新添加的功能')
  a()

(2).我们需要该函数a()在业务逻辑代码中调用的时候名称还是a()(函数的单一职责性),而不是decrotor(a),那么最简单的方法就是在把它赋值给a,即a = decorator(a)
(3). 但是在进行a = decorator(a)时,右边的decorator(a)函数会被直接执行,而此时逻辑代码还没有调用它,为避免该情况发生,我们使用闭包的思想。
(4). 这里使用闭包思想:也就是decorator(a)返回一个函数(后面我们会把这个函数写成warpper),将这个函数赋值给a,这样在进行a = decorator(a)时右边就不会被立即执行了。最后我们把需要添加的新功能,以及a()都写在warpper函数里,这样写就可以使得只有真正在业务逻辑代码中调用该函数的时候,warpper才会被执行,同时我们会加上一句a = decorator(a),来确保业务逻辑代码中调用的时候名称还是a()。所以写出的结果如下:

def a():
  print('i am a')

def decorator(a):
  def warpper():
    print('新添加的功能')
    a()
  return warpper

a = decorator(a)
a()
# 以上写法中
decorator(a) 等价于
def warpper():
    print('新添加的功能')
    a()

最后 Python给我们给定了以个语法糖@,即可以用@decorator 代替a = decorator(a),这也就演变出装饰器最终的写法:

def decorator(a):
  def warpper():
    print('新添加的功能')
    a()
  return warpper

@decorator
def a():
  print('i am a')

a()

>>>新添加的功能
>>>i am a

装饰器采用了闭包的思想,在装饰函数的同时不执行函数,只有到正正的业务逻辑代码调用的时候再执行函数。

2.2 装饰器的执行时间

当@decorator出现的时候,decorator装饰器函数就立即被执行了。

2.3 装饰器的执行顺序

从上到下去装饰,从下到上去执行。

@a
@b
@c
def func():
# 等效于
func = a(b(c(func)))

2.4 对有参数的函数进行装饰

如果需要被装饰的的函数a()中有参数呢?这时候就需要有一个东西来接收函数a()中的参数,而函数a()中的参数也可能是多种多样的,这时候我们可以这样来写warpper函数:warpper(*args, **kwargs),但是在warpper函数中调用的的func函数也需要用:func(**args, **kwargs)的写法来解包warpper中接收的函数,这样func才可以正常执行。代码如下:

def decorator(func):
  def warpper(*args, **kwargs):
    print('新添加的功能')
    func(*args, **kwargs)
  return warpper

@decorator
def a(i='我', am='是',):
  print(i, am, "a")
  
a()
>>>新添加的功能
>>>我 是 a

2.5 对有返回值的函数进行装饰

如果需要被装饰的的函数a()中有return返回值呢?这时候就需要有一个东西来返回函数a()中的返回值,所以我们在warpper中也应该写入return的部分,代码如下:

def decorator(func):
  def warpper(*args, **kwargs):
    print('新添加的功能')
    return func(*args, **kwargs)
  return warpper

@decorator
def a(i='我', am='是',):
  print(i, am, "a")
  b = i+am+"b"
  return b

b = a()
print(b)
>>>新添加的功能
>>>我 是 a
>>>我是b

原则上要保证,装饰器中的warpper函数的格式和被装饰的函数格式一致。

2.6 带有参数的装饰器

如果我们可以给装饰器本身传入一个参数,那么装饰器就可以随着传入参数的变化而做出不同的装饰功能,例如:print('新添加的功能')、print('新添加的技术')、print('新添加的颜色')。那么,该如何实现带有参数的装饰器呢? 思路:我们定义个一个外函数,把装饰器放入其中,然后装饰器中可以使用这个外函数传入的值,同时在外函数的最后返回其中的装饰器,那么这个整体就可以成为一个新的带有参数的装饰器。代码如下:

def zhuangshiqi(i):
  def decorator(func):
    def warpper(*args, **kwargs):
      print('新添加的'+i)
      return func(*args, **kwargs)
    return warpper
  return decorator

@zhuangshiqi(i="颜色而不是功能")
def a(i='我', am='是'):
  print(i, am, "a")
  return a

a()
>>>新添加的颜色而不是功能
>>>我 是 a

实例1:简单的装饰器

def decorator(func):
  def wrapper(*args, *kwargs):
    logging.warn("%s is running" %  func.__name__)
    return func(*args, *kwargs)
  return wrapper

@decorator
def a():
  print('i am a')

a()

>>> a is running
>>> i am a

实例2:装饰器用于验证权限的例子

userAge = 40

def canYou(func):
  def decorator(*args, **kwargs):
      if userAge > 1 and userAge < 10:
          return func(*args, **kwargs)
      print('你的年龄不符合要求,不能看')
  return decorator

@canYou
def play():
  print('开始播放动画片 《喜洋洋和灰太狼》')

play()

>>> 你的年龄不符合要求,不能看

内置装饰器

@staticmathod 、@classmethod 、@property

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

推荐阅读更多精彩内容