python装饰器

学习装饰器前,我们先了解三个概念:作用域、函数(嵌套)、闭包。

 

作用域

作用域简单说就是一个变量的命名空间。代码中变量被赋值的位置,就决定了哪些范围的对象可以访问这个变量,这个范围就是命名空间。python赋值时生成了变量名,当然作用域也包括在内。

在函数外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量

在函数内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量 ;

如果函数内的变量名与函数外的变量名相同,也不会发生冲突。

x = 66

def func():
    x = 88

x = 66这个赋值语句所创建的变量X,作用域为全局变量;

x = 88这个赋值语句所创建的变量X,它的作用域则为局部变量,只能在函数func()内使用。

尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用。

 

在 Python 函数中会创建一个新的作用域。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

>>> a_string = "This is a global variable"
>>> def foo():
...     print locals()
...
>>> print globals()
{'__builtins__': <module '__builtin__' (built-in)>, 'a_string': 'This is a global variable', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x00000000022DDAC8>, '__doc__': None}
>>> foo()
{}

内建函数 globals 返回一个包含所有 Python 能识别变量的字典。

调用了 foo 函数,在函数中打印局部变量的内容。从中可以看到,函数 foo 有自己单独的、此时为空的命名空间。

 

总结:

1、变量的作用域由代码被赋值的位置所决定

2、变量可以在3个不同地方,对应3种不同作用域:

  • 一个变量在函数内赋值,它的作用范围被定位在函数之内;
  • 变量在函数外赋值,它作用域就是当前整个文件的全局变量;
  • 当变量是在一个嵌套的函数中赋值时,对于这个嵌套的函数来说,这个变量是非本地的。

 

函数(嵌套)

Python中一个与众不同的语法就是可以嵌套函数,所谓嵌套,并不像其他语言中的在一个函数中调用另一个函数,而是在定义一个函数的时候,函数体里还能定义另一个函数。

内函数可以访问外函数的作用域,但不能重新赋值;外部函数不能访问内部函数的作用域。

例:

def foo():           # 定义函数foo(),
    m = 3            # 定义变量m=3;
    def bar():       # 在foo内定义函数bar()
        n = 4        # 定义局部变量n=4
        print m + n  # m 相当于函数bar()的全局变量
    bar()            # foo()函数内调用函数bar()

 

闭包

定义:如果一个内部函数里,对在外部作用域(单不是在全局作用域)的变量进行引用,那么内部函数就被认为闭包(closure)。

def  outer():
    x = 1
    def inner():
        print(x)  # 调用外部变量
    return inner  # 函数名作为返回值
	
# 执行inner函数的两种方式
outer()()

func = outer()
func()  # 相当于在外部执行inner函数

inner就是相对于outer的闭包函数

用途:

1、当闭包执行完后,仍然能够保持住当前的运行环境。

# 比如说,如果你希望函数的每次执行结果,都是基于这个函数上次的运行结果。我以一个类似棋盘游戏的例子来说明。
# 假设棋盘大小为50*50,左上角为坐标系原点(0,0),我需要一个函数,接收2个参数,分别为方向(direction),步长(step),
# 该函数控制棋子的运动。棋子运动的新的坐标除了依赖于方向和步长以外,当然还要根据原来所处的坐标点,用闭包就可以保持住这个棋子原来所处的坐标。
origin = [0, 0]    # 坐标系统原点
legal_x = [0, 50]  # x轴方向的合法坐标
legal_y = [0, 50]  # y轴方向的合法坐标


def create(pos=origin):

    def player(direction, step):
        # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
        # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
        new_x = pos[0] + direction[0]*step
        new_y = pos[1] + direction[1]*step
        pos[0] = new_x
        pos[1] = new_y
        # 注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
        return pos
    return player

player = create()          # 创建棋子player,起点为原点
print player([1, 0], 10)   # 向x轴正方向移动10步
print player([0, 1], 20)   # 向y轴正方向移动20步
print player([-1, 0], 10)  # 向x轴负方向移动10步

输出为

2、闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。比如有时我们需要对某些文件的特殊行进行分析,先要提取出这些特殊行。

def make_filter(keep): 
    def the_filter(file_name):  
        file = open(file_name)  
        lines = file.readlines()  
        file.close()  
        filter_doc = [i for i in lines if keep in i]  
        return filter_doc  
    return the_filter  

如果我们需要取得文件"result.txt"中含有"pass"关键字的行,则可以这样使用例子程序

filter = make_filter("pass")  
filter_result = filter("result.txt")  

 

参考博客:http://www.cnblogs.com/JohnABC/p/4076855.html

 

装饰器

概念:装饰器本质是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。

它经常用于有切面需求的场景,比如:插入日志、性能检测、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

==>装饰器的作用就是为已经存在的对象添加额外的功能。

 

普通装饰器

import time

def show_time(func):  # 这里外加一层函数目的就是给所需功能函数提供一变量,该形参变量就是对应需要添加功能的函数名
    def get_time():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Spend %s"%(end_time-start_time))
    return get_time

def index():
    print("hello Wolrd")
    time.sleep(3)

index = show_time(index)
index()

其中的show_time就是装饰器,将想要调用的函数包裹在里面,通过变量的形式来进行传递,func()表示调用的原函数。

Python为了表示更加方便,为我们提供了语法糖,将上述index()=show_time(index) =封装=> @show_time

import time

def show_time(func):
    def get_time():
        start_time = time.time()
        func()
        end_time = time.time()
        print("Spend %s"%(end_time-start_time))
    return get_time

@show_time
def index():
    print("hello Wolrd")
    time.sleep(3)
index()

提示:

  1. 这里需要注意的问题: index=show_time(index)其实是把get_time引用的对象引用给了index,而get_time里的变量func之所以可以用,就是因为get_time是一个闭包函数。
  2. Python装饰器如此方便归功于Python函数能够像普通对象一样作为参数传递给其他函数,函数名作为变量、返回值、定义在另外一个函数内。

 

含参装饰器

import  time

def show_time(func):
    def get_time(x, y):
        start_time = time.time()
        func(x, y)
        end_time = time.time()
        print("Spend %s"%(end_time-start_time))
    return get_time

@show_time
def index(x, y):  # 这里添加参数
    print("hello Wolrd")
    time.sleep(3)
    print(x + y)

index(1,3)

分析:

  1. 先来解封@show_time ==>index = show_time(index)
  2. 执行函数index(x, y),即执行函数get_time(x, y)
  3. 与此同时show_time(func)传递的是index(x, y)函数本身,因此对应的func(x, y)同样要带有对应的参数

 

动态参数装饰器

import time

def show_time(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print('spend %s' % (end_time-start_time))
    return wrapper

@show_time  # add=show_time(add)
def add(*args, **kwargs):
    time.sleep(1)
    sum = 0
    for i in args:
        sum += i
    print(sum)

add(1, 2, 3, 4)

 

带参数的装饰器

import time

def time_logger(flag=0):  # 新添加的外层函数
    def show_time(func):  # 原外层函数
        def wrapper(*args, **kwargs):  # 功能函数
            start_time = time.time()
            func(*args, **kwargs)  # 调用函数
            end_time = time.time()
            print('spend %s' % (end_time - start_time))
            if flag:
                print('将这个操作的时间记录到日志中')
        return wrapper
    return show_time

@time_logger(1)
def add(*args, **kwargs):
    time.sleep(1)
    sum = 0
    for i in args:
        sum += i
    print(sum)

add(1, 2, 3)

根据上面代码,@time_logger(1) 和@time_logger(0)结果不同,@time_logger(1)的结果比@time_logger(0)结果多输入一行内容“将这个操作的时间记录到日志中”。

分析:

  1. 调用time_logger()函数返回的闭包函数的函数名:show_time,此时结果为:@show_time,time_logger()中的参数就是为内部函数提供可用变量
  2. 下层解封就是和前面的过程一样

 

多层装饰器

def makebold(fn):
    def wrapper():
        return "<b>" + fn() + "</b>"
    return wrapper

def makeitalic(fn):
    def wrapper():
        return "<i>" + fn() + "</i>"
    return wrapper

@makebold    # 装饰器1
@makeitalic  # 装饰器2
def hello():
    return "hello Siffre"

print hello()

执行结果

分析:

  • 从最里面的装饰器依次向外执行
  • 前面的装饰器函数名都会作为下一个装饰器函数的参数
  • 结果是多个装饰器的组合

 

posted @ 2017-01-19 10:57  shhnwangjian  阅读(328)  评论(0编辑  收藏  举报