Python Enclosing作用域、闭包、装饰器

来源:互联网 发布:php 分割成数组 编辑:程序博客网 时间:2024/06/04 19:26

Enclosing作用域、闭包

Jaglawz: 听讲Python一切都是对象,是吗?

Pylego: 是的,像函数也是对象。

Jaglawz: 那么函数也可以有自己的属性了?

Pylego: 当然,像下面这样写是可以的:

def foo():    print('I am foo')def bar():    print('I am bar')foo.bar = barfoo.bar()

Jaglawz: 这都行,那是不是函数也可以像普通对象一样当作参数传递也可以当作对象来返回?

Pylego: 是的,比如下面的用法:

def deco(func):    string = 'I am deco'    def wrapper():        print(string)        func()    return wrapperdef foo():    print('I am foo')foo = deco(foo)foo()  """输出:I am decoIam foo"""

Jaglawz: 好吧,当作参数传递和返回我理解了,但是我对wrapper函数的print(string)这个string的查找感到迷惑。

Pylego: 不错嘛!这都被你看出来了,那你知道Python作用域的LEGB原则吗?

Python中的作用域 Python 中,一个变量的作用域总是由在代码中被赋值的地方所决定的。 当 Python
遇到一个变量的话他会按照这样的顺序进行搜索: 本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing
locals)→全局/模块作用域(Global)→内置作用域(Built-in) Jaglawz:
我知道是知道可以我就是对那个E(Enclosing)作用域不是很理解。

Pylego: 那就对了,你可以在刚才代码的基础上运行下面的代码:

print(foo.__closure__)# 输出:(<cell at 0x7fc50f45afd8: function object at 0x7fc50f4168c0>, <cell at 0x7fc50f45aec0: str object at 0x7fc50c065fc0>)

Jaglawz: 咦,这两个内存地址是啥家伙?

Pyelgo: 这就是wrapper函数引用的外层函数(就是deco函数啦)的两个变量:string和func啊!

Jaglawz: 也就是说内层函数(在本例中就是wrapper啦)会把外层函数(在本例用就是deco啦)作用域里面的对象放到__closure__属性里,以供自己查找?

Pylego: 是的,但是不是所有外层函数作用域的对象都会放到内层函数的__closure__属性里,仅限自己用到的,这个__closure__就是enclosing作用域啦!

Jaglawz: 原来enclosing作用域是这样的,明白了。

Pyelgo: 如果内部函数引用到外层函数作用域的对象,这个内部函数就称为闭包。

Jaglawz: 原来闭包就是这家伙,很简单嘛!

当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:

1.必须有一个内嵌函数
2.内嵌函数必须引用外部函数中的变量
3.外部函数的返回值必须是内嵌函数

这里写图片描述

装饰器

Jaglawz: 咦,我想到内部函数有一个妙用,你看看是不是这样啊,比如说我想输出一个函数的运行时间又不想去破坏这个函数的代码,是不是可以这样写:

import timedef time_machine(func):    def wrapper(*args, **kwargs):        start_time = time.time()        func(*args, **kwargs)        print(u'共耗时: %s秒' % (time.time()-start_time))    return wrapperdef foo():    time.sleep(3)foo = time_machine(foo)foo()

Pylego: 你这智商要冲出宇宙的节奏啊!但是Python的开发者早就想到每次foo = time_machine(foo)很麻烦,特地为你准备了语法糖,来接糖:

import timedef time_machine(func):    def wrapper(*args, **kwargs):        start_time = time.time()        func(*args, **kwargs)        print(u'共耗时: %s秒' % (time.time()-start_time))    return wrapper@time_machinedef foo():    time.sleep(3)"""也就是说:@time_machinedef foo():    pass相当于: foo = time_machine(foo),这里是重点,这里是重点,这里是重点,以后的谈话能不能理解就看你对这个语句可理解!"""foo()

Pylego: time_machine就是装饰器(decorator),名字起得多形象啊,装饰函数嘛!

Jaglawz: 你这么一说,我就觉得装饰器咋这么简单呢!问题是我看到很多人的装饰器还带参数,还有人用类当装饰器,这又是咋回事呢?

Jaglawz: 我经常看到有人的装饰器是带参数的,这又是咋回事呢?

Jaglawz: 我经常看到有人的装饰器是带参数的,这又是咋回事呢?

Pylego: 这个其实很简单的,你还记得上次我说

@decodef foo():    pass # 相当于: foo = deco(foo) # 那么@new_deco(*args,**kwargs)def bar():    pass# 相当于: bar = new_deco(*args, **kwargs)(bar)

Jaglawz: 也就是说,new_deco返回的是一个装饰器函数,然后再去装饰其他函数。那类装饰器又是怎么回事呢?

Pylego: 你知道Python的对象可以像函数一样调用吗?

from hashlib import sha256class HashCache(object):    def __init__(self):        self.cache = {}    def __call__(self, string):        if string not in self.cache:            self.cache[string] = sha256(string).hexdigest()        return self.cache[string]hc = HashCahce()hc('foo')  # 像函数一样调用hc对象hc('foo')hc('bar')Jaglawz: 如果是这样的话我就明白了。class FibCache(object):    def __init__(self, func):        self.func = func        self.cache = {}    def __call__(self, n):        if n not in self.cache:            self.cache[n] = self.func(n)        return self.cache[n]@FibCachedef fib(n):    if n == 0:        return 0    elif n == 1:        return 1    else:        return fib(n-1) + fib(n-2)# 相当于: fib = FibCache(fib)# fib(10)相当于FibCache(fib)(10)# 装饰后的fib是FibCache的一个对象而已# 也就是说作为装饰器的类的构造方法要接收一个待装饰的函数,然后__call__函数的参数要和待装饰的函数的参数是一样的(除了self),这样的类就可以用来装饰函数了

Pylego: 你这个装饰器厉害,还给fibnacci函数加了缓存,佩服!

Jaglawz: 我不会告诉你我是看了大神的杰作然后吓吓唬吓唬你的!