精讲由python闭包讲到装饰器(原创)

部分借鉴:python中闭包,闭包的实质
内外函数:
在一个函数内部嵌套地定义了若干个函数,外部的函数我们叫它外函数,内部的函数我们叫它内函数
闭包:
嵌套函数中,内函数里使用了外函数中的变量,并且外函数的返回值是内函数的函数名,这样就构成了一个闭包(注意虽然常见只有内嵌一个内函数,但闭包不仅仅只能内嵌一个内函数,将若干内函数通过容器对象一次性地return也是可以的,我看java的闭包也是这么干的)。

# 闭包函数的简单实例
# outer是外函数 inner是内函数
def outer(a):
    b = 10   # a和b都是外函数的变量
    def inner():
        # 在内函数中 用到了外函数的变量b
        print(a + b)
        
    # 外函数的返回值是内函数的函数名(注意不带括号)
    return inner

if __name__ == '__main__':
    demo = outer(5)
     """
     我们看看这一步发生了什么:
     调用外函数outer并传入5作为形参a的值,此时内存中将分配一个outer函数栈帧,在
     栈帧里将会产生两个变量 a=5 和 b=10,以及为内部函数代码块分配一块内存fmem,
     在outer函数结束时将指向这块内存的函数名inner返回给调用者,此时全局变量demo
     和inner一样指向fmem,这导致outer函数虽然结束了,但由于outer函数栈帧里面存
     在外部引用所以整个outer函数栈帧都不会被释放掉。如下图所示
     """
    demo() 
     """
     demo()相当于inner(),此时内存中将分配一个inner函数栈帧,并根据LEGB变量查找
     规则引用外函数的a和b,执行print(a+b),内函数inner执行完毕,如果对全局变量
     demo没有后续操作,整个程序将会结束,程序结束过程是:先释放inner函数栈帧再
     释放outer函数栈帧,然后释放整个程序的资源,至此,整个程序执行完毕。
     """

闭包的作用:

1,面向对象:
先试想一下一种需求,在非面向对象只有函数的语言如JavaScript中,当我需要定义一个如下功能的变量:

(1)它只能被指定的若干函数引用,对其它函数(包括其它模块的函数)不可见。
(2)它能保持函数上一次调用它的状态。

如果仅仅依靠普通函数这几乎不可完成,而借助闭包这样的方式就可以,嵌套函数中可以定义一个外函数变量X,内嵌有若干内函数,并且内函数使用了此变量X,外函数返回内函数的函数名,先调用外函数得到内函数,然后调用内函数,此时虽然外函数执行完毕但内函数的执行还能利用外函数环境,X变量只能内函数可见,可以在每次调用内函数时修改X以保持函数每一次调用它的状态了。这种把方法和数据封装在一起的思想和面向对象封装的思想很像是吧,没错!它就是面向对象的早期雏形,在今天仍然是许多非面向对象的语言如JavaScript用来封装数据的方式(甚至是唯一的方式)。

python已经有类来封装了,那闭包对python来说还有啥用?答案是装饰器!
2,装饰器:
本章部分参考Python 装饰器(decorator)
装饰器是做什么的??Python的装饰器本质上是一个Python函数,它可以让使用它装饰的函数在不需要做任何代码变动的前提下增加额外功能,就连调用都不用做任何样式的改变。其中一个应用场景就是,我们需要用来统计并打印某些函数执行耗时时间的功能,你可能会这样做(用非装饰器方法):

import time

def time_counter(fun):
    start = time.time()
    fun()
    print(time.time() - start)

def test_fun():
    for i in range(1, 100000000):
        pass
        
if __name__ == "__main__":
	time_counter(test_fun)   #  1.616828203201294

下面使用装饰器来实现,装饰器函数就是一个闭包函数

import time

def decorator_time_counter(fun):  # 别忘了形参fun也是外函数变量

    def wrapper():
        start = time.time()
        fun()
        print(time.time() - start)
    return wrapper

@decorator_time_counter
def test_fun():
    for i in range(1, 100000000):
        pass
        
if __name__ == "__main__":
	test_fun()   #1.7541477680206299

装饰器语法糖@:
如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖。
它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在解释器解析这个函数的时候(注意不是调用的时候),会将被装饰的函数作为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数 或 装饰器,然后会建立一个test_fun函数名同名的同作用域的变量test_fun指向wrapper函数,在调用test_fun()时其实调用的是wrapper() 。如下图所示:
在这里插入图片描述

装饰器的使用:
是在目标函数前面一行加"@装饰器函数名"即可。现在只要直接调用test_fun()就可以了。调用的地方以及test_fun()内部都不要作任何修改。
装饰器的本质:
使用“@函数装饰器名称”修饰原函数,等同于创建与原函数名称相同的变量(同作用域)来关联内嵌函数;故调用原函数时执行内嵌函数。

原函数名称 = 函数装饰器名称(原函数名称)
这里相当于 test_fun=decorator_time_counter(test_fun)

执行顺序分析:
经断点调试分析发现:
@decorator_time_counter在加载时就已经先执行decorator_time_counter()函数并将被修饰目标函数名test_fun作为闭包函数的外函数变量传入,返回内函数名wrapper,此时已然相当于test_fun=wrapper。

当被修饰目标函数test_fun被调用时,那也就是wrapper的调用,实参列表(这里无参数)将作为wrapper的实参列表。

目标函数带参数的装饰器函数写法,使用(*args, **kwargs)处理不定参:
#此装饰器的功能是简单地验证用户名--只有Lisa才可正确输出! 
#现实中处理网络验证也很常见。

def decorator_user_auth(fun):  # 别忘了形参fun也是外函数变量
    def wrapper(*args, **kwargs):
        if args[0] != "Lisa":
            print("name error!")
            # you can do something here or just return None
        fun(*args, **kwargs)     
    return wrapper

@decorator_user_auth
def test_fun(name, age):
    for i in range(1, 100000000):
        pass
    print(name, age)

if __name__ == "__main__":
    test_fun("Hacker", 23)  # name error!

装饰器带参数,即"@装饰器函数(*args, **kwargs)"的形式:

需要在闭包函数外再封装一层函数接收和处理装饰器的参数,其本质也还是个闭包函数:

def decorator_args_test(*args, **kwargs):
    print(*args, *kwargs)

    def decorator_user_auth(fun):  
        def wrapper(*args, **kwargs):
            if args[0] != "Lisa":
                print("name error!")
                return None
            fun(*args, **kwargs)
        return wrapper
    return decorator_user_auth


@decorator_args_test(1, 2)
def test_fun(name, age):
    for i in range(1, 100000000):
        pass
    print(name, age)


if __name__ == "__main__":
    test_fun("Hacker", 23) 

结果:

1 2
name error!
[Finished in 0.1s]

执行顺序分析:
经断点调试发现,decorator_args_test(1, 2)在加载的时候就已经先执行了,它返回的是内部嵌套的装饰器函数的函数名decorator_user_auth,然后执行test_fun=decorator_user_auth(test_fun)返回wrapper函数名,此时test_fun=wrapper,当被修饰目标函数test_fun被调用时,那也就是wrapper的调用,实参列表将作为wrapper的实参列表。内存图如下所示:
在这里插入图片描述

装饰器链:
一个函数可以被多个装饰器修饰,执行顺序为从近到远

@decorator1
@decorator2
@decorator3
def func():
    ...

上面程序的执行顺序是从近到远,所以它等效于下面这行代码:
decorator1( decorator2( decorator3(func) ) )
比如我们把上面“验证用户名”和”统计函数耗时时间“的两个装饰器一起用:

import time


def decorator_time_counter(fun):  
    def wrapper(*args, **kwargs):
        start = time.time()
        fun(*args, **kwargs)
        print("decorator_time_counter...")
        print(time.time() - start)
    return wrapper


def decorator_user_auth(fun): 
    def wrapper(*args, **kwargs):
        if args[0] != "Lisa":
            print("name error!")
            # you can do something here or just return None
        fun(*args, **kwargs)
        print("decorator_user_auth...")
    return wrapper

@decorator_time_counter
@decorator_user_auth
def test_fun(name, age):
    for i in range(1, 100000000):
        pass

if __name__ == "__main__":
    test_fun("Lisa", 23)

结果:

decorator_user_auth...
decorator_time_counter...
1.607548713684082
[Finished in 1.7s]

经验证,确实装饰器执行顺序是从近到远。

其它装饰器种类

参考:python常用内置装饰器大全总结【@property、@x.setter、@x.deleter】
事实上,不仅函数可使用装饰器,类也可使用装饰器,类方法也可使用装饰器,类属性也可使用装饰器。
比如:python内置装饰器就有属性(property),类方法(classmethod),静态方法(staticmethod)
1)属性(property)
property可以将python定义的函数当做属性访问,从而提供更加友好访问方式,但是有时候setter/deleter也是需要的
只有@property表示只读。
同时有@property和@x.setter表示可读可写。
同时有@property和@x.setter和@x.deleter表示可读可写可删除。

class Foo:
    def __init__(self, name):
        self.__name = name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise TypeError('name must be str')
        self.__name = value
    @name.deleter
    def name(self):
        raise TypeError('can not delete')
f = Foo('jack')
print(f.name)  # jack
f.name = 'hanmeimei'
print(f.name)  # hanmeimei
# del f.name  # TypeError: can not delete

2)类方法(classmethod),静态方法(staticmethod)就不举例了。

3)自定义的类装饰器举例

def outer(cls):  # 外函数接收的是类名
    def inner(*args, **kwargs):
        cls.begin = "begin"    #给类添加一个类变量
        obj = cls(*args, **kwargs)
        cls.end = "end"
        return obj
    return inner

@outer
class MyClass:
    def __init__(self, a, b):
        self.a = a
        self.b = b


a = MyClass(1, 2)

# 相当于:
# class MyClass:
#     pass
# inner = outer(MyClass)
# a = inner()


print(a.begin)
print(a.end)

3,用装饰器实现单例模式

什么是python的单例模式,所谓单例模式就是一个类只能创建一个实例化。
python学习教程(十三)python实现单例模式

首先,要知道什么是python的单例模式,所谓单例模式就是一个类只能创建一个实例化。

然后,就是python单例模式的方法,总共可以分为两大种,四小种,一会就回说的。
下面使用了__metaclass__属性,不懂请参考Python中的元类metaclass

首先,方法一:
在元类(对象工厂)中定义一个变量标记是否已经创建一个类,是就什么也不做,不是就创建一个实例返回,这样一个类只有一个实例。

class Singleton(type):  #继承自type元类
    def __init__(cls, name, bases, dict):
		super(Singleton, cls).__init__(name, bases, dict)
		cls.instance = None
 
    def __call__(cls, *args, **kw):
		if cls.instance is None:
		    cls.instance = super(Singleton, cls).__call__(*args, **kw)
		    return cls.instance
 
class MyClass(object):
    __metaclass__ = Singleton
 
print MyClass()
print MyClass()

方法二:使用装饰器(decorator)

def singleton(cls, *args, **kw):  
    instances = {}  
    def _singleton():  
        if cls not in instances:  
            instances[cls] = cls(*args, **kw)  
        return instances[cls]  
    return _singleton  
 
@singleton  
class MyClass(object):  
    a = 1  
    def __init__(self, x=0):  
        self.x = x  
  
one = MyClass()  
two = MyClass()  
  
two.a = 3  
print one.a  
#3  
print id(one)  
#29660784  
print id(two)  
#29660784  
print one == two  
#True  
print one is two  
#True  
one.x = 1  
print one.x  
#1  
print two.x

方法三:使用__metaclass__元类来实现

class Singleton2(type):  
    def __init__(cls, name, bases, dict):  
        super(Singleton2, cls).__init__(name, bases, dict)  
        cls._instance = None  
    def __call__(cls, *args, **kw):  
        if cls._instance is None:  
            cls._instance = super(Singleton2, cls).__call__(*args, **kw)  
        return cls._instance  
  
class MyClass(object):  
    __metaclass__ = Singleton2  
  
one = MyClass()  
two = MyClass()  
  
two.a = 3  
print one.a  
#3  
print id(one)  
#31495472  
print id(two)  
#31495472  
print one == two  
#True  
print one is two  
#True  

方法四:通过共享属性来实现,所谓共享属性,最简单直观的方法就是通过__dict__属性指向同一个字典dict

class Borg(object):  
    _state = {}  
    def __new__(cls, *args, **kw):  
        ob = super(Borg, cls).__new__(cls, *args, **kw)  
        ob.__dict__ = cls._state  
        return ob  
  
class MyClass(Borg):  
    a = 1  
  
one = MyClass()  
two = MyClass()  
  
#one和two是两个不同的对象,id, ==, is对比结果可看出  
two.a = 3  
print one.a  
#3  
print id(one)  
#28873680  
print id(two)  
#28873712  
print one == two  
#False  
print one is two  
#False  
#但是one和two具有相同的(同一个__dict__属性),见:  
print id(one.__dict__)  
#30104000  
print id(two.__dict__)  

其实吧,从本质上来讲,方法一二三都属于一种单例化模式的方法,与第四种不同,所以认为python中有两种或四种方法实现单例模式都可以。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值