从变量作用域角度一步步理解python装饰器

    装饰器是用于封装函数或类的工具,在某些应用场景下,如插入日志、性能测试、事务处理、权限校验等业务逻辑中的到很好应用,比如Flask的路由表即用了装饰器。有利于减少重复代码,可扩展性强。

本篇主要介绍如下:

    1) python的局部变量和全局变量

    2)python 闭包,闭包与变量、装饰器关联

    3)python 装饰器


一、python的局部变量和全局变量

    全局变量是在整个py文件中都起作用的,局部变量是在函数内定义,只在函数内起作用的变量。

1.1 局部变量和全局变量同一个名字,修改局部变量,全局变量不会改变。

#!/usr/bin/python  
# -*- coding: utf-8 -*-  
b = 6
def f1(a):
	print a
	b = 5
	print b
f1(3)
print b

结果

3

5

6

1.2.   函数内部直接使用未定义局部变量会出错

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
b = 6
def f1(a):
      print a
      print b
      b = 5

f1(3)

结果:

F:\test>python test.py

  File"test.py", line 9

    b = 5

        ^

IndentationError: unindent does not match anyouter indentation level

    原因是,python在编译test.py的时函数f1时,会判断b是局部变量,但是打印b时,发现没有绑定值。

1.3.     函数内使用全局变量,用global修饰

#!/usr/bin/python 
# -*- coding: utf-8 -*-  

b = 6

def f1(a):
      globalb
      printb
      b =5   

f1(3)
print b 

结果:

6

5

二、闭包

闭包是指延伸了作用域的函数,它会保留定义函数时存在的自由变量的绑定。

#!/usr/bin/python 

# -*- coding: utf-8 -*- 

def count():

   a = 1

   def sum():

        c = 1

        return a + c  # a为自由变量

   return sum

count_tmp = count()

print count_tmp ()

结果:

2

    函数count整体是闭包,a是自由变量,按以上,a为局部变量,调用sum函数时a已经返回了,但是依旧可以使用a的绑定。

     可变对象与不可变对象参考:https://www.cnblogs.com/sun-haiyu/p/7096918.html

    python中不可变对象指对象指向的内存中的值不能被改变,当操作该对象时,会重新复制一份再改变,如数值类型intfloat、字符串str、元组tuple都是不可变对象。

    可变对象是指可以被操作地址不会改变的对象,如列表、字典、集合set

2.1 自由变量为可变对象的闭包

   如下面例子,自由变量为列表。

#!/usr/bin/python 

# -*- coding: utf-8 -*-  

from __future__ import division 

def make_averager():

      series= []    

      defaverage(new_value):

           series.append(new_value)

           total= sum(series)

           returntotal / len(series)          

      returnaverage     

avg = make_averager()

print avg(10)

print avg(11)

print avg(12)

结果:

10.0

10.5

11.0

    from __future__ import division 是采用精确除法,可以看到,调用make_averager()时,返回一个average对象,每次调用average,会把历史加入的新值保存在可变对象average中,然后计算当前平均值。

2.2 自由变量为不可变对象闭包

#!/usr/bin/python 
# -*- coding: utf-8 -*- 
from __future__ import division
def make_averager():
       count= 0
       total= 0   
       defaverage(new_value):

              count+= 1

              total+= new_value

              returntotal / count            

       returnaverage

avg = make_averager()

print avg(10)

结果:

t0 is 2.05261256527e-06

will sleep 5 seconds

t1 is 5.00363380844 

    因为count为不可变对象,当对count操作加1时,会将自由变量count变成局部对象,此时局部对象count还未被赋值就进行使用导致出错。

    在闭包中,不可变对象只能读取,不能更新,如果更新了,就隐式创建了局部变量,此变量不会保存在闭包中了。

    解决方法:Python 3引入了nonlocal声明,它的作用是将变量声明为自由变量,即使对nonlocal声明的变量赋予新值,也会变成能自由变量,保存在闭包中。

 

三、装饰器

3.1 装饰器与函数交互

#!/usr/bin/python 

# -*- coding: utf-8 -*-  

import time 

def clock(func):

      defclocked(*args):

           t0= time.clock()

           print"t0 is %s"% t0

           result= func(*args)

           t1= time.clock()

           print"t1 is %s" % t1

           returnresult

      returnclocked

@clock

def snooze(seconds):

      print"will sleep %s seconds" % seconds

      time.sleep(seconds)

snooze(5)

结果:

t0 is 2.05261256527e-06

will sleep 5 seconds

t1 is 5.00363380844


@clock 是装饰器语法,代表一个装饰器,snooze是被装饰的函数,

@clock

def snooze(seconds):

      print"will sleep %s seconds" % seconds

      time.sleep(seconds)

等价于:

def snooze(seconds):

      print"will sleep %s seconds" % seconds

      time.sleep(seconds)

snooze = clock(snooze)

    clock整体可以看做是一个闭包,func为自由变量,因此clocked可以使用此自由变量。调用clock,返回clocked,那么调用snooze(5)相当于调用clocked(5)。此时snooze变成了clocked的引用。

    因此此处装饰器的作用是保证clocked()函数打印出程序运行时间:  

t0 is2.05261256527e-06

t1 is 5.00363380844

    又通过被装饰函数snooze增加休眠5秒的功能:

 will sleep 5 seconds

参考资料《流畅的Python》

猜你喜欢

转载自blog.csdn.net/jishuwenming/article/details/80920758