python从闭包到装饰器

前面写过一篇《原来这就是python装饰器》,主要介绍蛇什么是装饰器,这一篇主要介绍装饰器所运用到的技术原理,最终说明装饰器的工作原理!

python如何判断变量是不是局部变量?

如下的例子:

b  = 6

def f1(a):
    print(a)
    print(b)
    b = 9
    print(b)

if __name__ == "__main__":
    f1(3)

上例中,我们的程序会输出3并报错:UnboundLocalError: local variable 'b' referenced before assignment。这是因为python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

想要解决上面的报错问题,需要将变量b声明为global,也就是全局变量。

b  = 6

def f1(a):
    global b
    print(a)
    print(b)
    b = 9
    print(b)

if __name__ == "__main__":
    f1(3)

此时顺序输出3、6、9。

python何时执行装饰器?

registry = []

def register(func):
    print('running register {}'.format(func))
    registry.append(func)
    return func

@register
def f1():
    print('running f1()')

@register
def f2():
    print('running f2()')

def f3():
    print('running f3()')

def main():
    print('runing main()')
    print('registry -> ', registry)
    f1()
    f2()
    f3()

if __name__ == "__main__":
    main()

我们看看上例的输出。

running register 
running register 
runing main()
registry ->  [, ]
running f1()
running f2()
running f3()

我们看到,在main函数执行之前,就有两行输出。这说明装饰器在加载模块时立即执行,而被装饰的函数只在明确调用时运行。并且我们要知道装饰器能把被装饰的函数替换成其他函数。

闭包

我们还是直接上代码来说明什么时闭包?

def make_averager():
    series = []

    def averager(value):
        series.append(value)
        total = sum(series)
        return total/len(series)
    return averager

if __name__ == "__main__":
    avg = make_averager()
    print(avg(5))
    print(avg(10))
    print(avg(15))

上述程序的输出依次是5、7.5、10,上面的make_averager()函数就形成了一个闭包。

所谓闭包就是在一个外函数中定义一个内函数,此例中,make_averager()就是外函数,averager()就是内涵数,在内函数中运用了外函数的临时变量,并且外函数的返回值是内涵数的引用,这样就构成了一个闭包。而这个临时变量就会series,返回就是averager就是内函数的引用。

对于一个函数,如果函数名后面紧跟一个括号,相当于现在我就要调用这个函数;如果不跟括号,相当于只是一个函数的名字,这个时候里面存放的是函数所在的位置的引用。

在上面这个闭包函数的实现中,我们利用可变对象series来实现变量的访问。如果这个变量是数字、字符串、元组等不可变类型,只能读取,不能更新,这是我们将无法在内函数中访问外函数中定义的变量。如何解决呢?

nonlocal声明

在python3中,我们引入了nonlocal声明来解决变量不能访问的问题!

def make_averager():
    series = 0
    count = 0

    def averager(value):
        nonlocal series,count
        series += value
        count += 1
        return series/count
    return averager

此时将series定义为nonlocal,就可以实现正常的访问了!这个时候有一个问题,此时我们能不能将series定义为global呢?如果我们将series定义为global类型,就会报错:NameError: name 'series' is not defined。这是为什么?还是没有弄懂,一个坑,如果有谁看到这篇博客,并且知道原因,请留言告诉我,万分感谢!不过我们将程序改为下面的样子,可以输出正确值!

def make_averager():
    global series, count
    series = 0
    count = 0

    def averager(value):
        global series,count
        series += value
        count += 1
        return series/count
    return averager

下面来说说global和nonlocal的区别!global表示将变量声明为全局变量;nonlocal表示将变量声明为外层变量(外层函数的局部变量,而且不能是全局变量)。

标准库中的装饰器

functools.wraps

def logged(func):
    def with_logging(*args,**kwargs):
        print(func.__name__ + "was callled")
        return func(*args,**kwargs)
    return with_logging

@logged
def f(x):
    return x + x*x

f = logged(f)
print(f.__name__)

如上例所示,在被装饰器修饰之后,这个函数的输出为:with_logging,没有它原本的功能信息。这个时候,我们利用functools.wraps可是实现将原函数对象的指定属性复制给包装函数对象。

from functools import wraps

def logged(func):
    @wraps(func)
    def with_logging(*args,**kwargs):
        print(func.__name__ + "was callled")
        return func(*args,**kwargs)
    return with_logging

@logged
def f(x):
    return x + x*x

f = logged(f)
print(f.__name__)
#输出为f

property

property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

class LineItem:
    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price
        
    def subtotal(self):
        return self.weight * self.price
    
    def get_weight(self):
        return self.__weight
    
    def set_weight(self, value):
        if value > 0:
            self.__weight = value
        else:
            raise ValueError("value must be > 0")
        
    weight = property(get_weight, set_weight)

上例中我们不使用装饰器,写一个简单的计价程序。如下使用装饰器,就是下面的样子。

class LineItem:
    def __init__(self, description, weight, price):
        self.description = description
        self.weight = weight
        self.price = price

    def subtotal(self):
        return self.weight * self.price

    @property
    def weight(self):
        return self.__weight

    @property
    def weight(self, value):
        if value > 0:
            self.__weight = value
        else:
            raise ValueError("value must be > 0")

可以看到,相对于第一种写法,第二种要简单!

staticmethodclassmethod

我们知道对于一个普通的类,我们要使用其中的函数的话,需要对类进行实例化;而一个类中,某个函数前面加上了staticmethod或者classmethod的话,那么这个函数就可以不通过实例化直接调用。

这两个方法本身有什么区别呢?

<1>@staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。

<2>@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。

<3>如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。

<4>而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。

简单来说,就是staticmethod不需要传入表示自身对象参数,而classmethod需要传入一个类似于self的cls参数。那么既然classmethod麻烦一点,必然有他麻烦的道理,就是:使用classmethod的话,凡是在该类中的类的属性,方法,实例化对象等,都可以调用出来。

你可能感兴趣的:(Python,闭包,装饰器,staticmethod,classmethod)