decorators

前言

在python当中,有一个非常好的特性,叫做装饰器,装饰器用于在方法或类前面进行装饰,并在声明或者调用的时候,执行装饰器的相关操作。装饰器的实现方式就是闭包,这两个东西都是我不怎么了解的,因此做一个笔记。

闭包

闭包广泛使用在函数式编程语言中,虽然不是很容易理解,但是又不得不理解。

闭包是什么?

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
—— 维基百科)

一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

从形式上看,闭包其实和普通函数的区别:

  1. 普通函数传递变量,闭包传递函数;
  2. 闭包的封装性更好,调用的参数更少。

具体说明

#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer(a):
    # inner是内函数
    def inner(b):
        #在内函数中 用到了外函数的临时变量
        print(a+b)
    # 外函数的返回值是内函数的引用
    return inner

if __name__ == '__main__':
    # 在这里我们调用外函数传入参数5
    #此时外函数临时变量 a是5  ,并创建了内函数,然后把内函数的引用返回存给了demo
    # 外函数结束的时候发现内部函数将会用到自己的临时变量,这个临时变量就不会释放,会绑定给这个内部函数
    demo = outer(5)
    # 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
    # demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
    demo(10) # 15

在闭包内函数中,我们可以随意使用外函数绑定来的临时变量,但是如果我们想修改外函数临时变量数值的时候发现出问题了!咋回事捏??!!(哇哇大哭)

在基本的python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:1. global 声明全局变量;2. 全局变量是可变类型数据的时候可以修改。在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:

  1. 在python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。

  2. 在python2中,没有nonlocal这个关键字,我们可以把闭包变量改成可变类型数据进行修改,比如列表。

上两份代码:一份声明nonlocal,一份没有。

#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer(a):
    # inner是内函数
    def inner(b,ischange):
        #在内函数中 用到了外函数的临时变量
        if ischange:
            nonlocal a    #声明了nonlocal
            a=1
        print(a+b)
    # 外函数的返回值是内函数的引用
    return inner

if __name__ == '__main__':
    demo = outer(5)
    demo(10,ischange=True) # 15
    demo(10,ischange=False)

输出:

11
11

当输入为False的时候,还是输出11,证明,内函数中的a并没有被释放,一直存在,保留了上次a的值。因此,如果修改了,要么手动改回来,要么重新获得一个demo。

如果没有声明为nonlocal,程序就会报错。如下所示。

11
Traceback (most recent call last):
  File "/home/liuxin/Documents/CV/Python-Note/decorators/decorators.py", line 31, in <module>
    demo(10,ischange=False)
  File "/home/liuxin/Documents/CV/Python-Note/decorators/decorators.py", line 18, in inner
    print(a+b)
UnboundLocalError: local variable 'a' referenced before assignment

因为a指向的是一个常数,在第一次运行的时候,将a的引用指向了另一个常数,然后a本身相当于是内函数的变量。在第二次运行的时候,a已经是一个内函数的变量了,跟outer没有任何关系。所以会显示找不到。

问题和应用

这就是闭包的作用了。但其实,这样做有个问题,就是demo函数一直存在,a也一直存在,除非demo指向了其余对象,否则闭包一直存在,占用这程序的内存。要那么就是手动del,要么就是demo指向其余对象,然后之前的指向被垃圾回收机制,自动回收。

闭包最常见的用处就是用于装饰器,接下来介绍python独有的装饰器Decorators。

装饰器

装饰器就是闭包操作的最好的应用。

装饰器是什么?

装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。——菜鸟教程

具体说明

如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖。

它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰函数装饰器

你要问我装饰器可以实现什么功能?我只能说你的脑洞有多大,装饰器就有多强大。

普通用法

装饰器的使用方法很固定:

  • 先定义一个装饰函数(帽子)(也可以用类、偏函数实现)。
  • 再定义你的业务函数、或者类(人)。
  • 最后把这顶帽子带在这个人头上。

装饰器的简单的用法有很多,这里举两个常见的。

  • 日志打印器。
def logger(func):
    def wrapper(*args,**kw):
        print('{} is called...'.format(func.__name__))
        func(*args,**kw)
    return wrapper

@logger
def add(x,y):
    print(x+y)

if __name__ == '__main__':
    add(3,5)
  • 时间计时器。
# 这是装饰函数
def timer(func):
    def wrapper(*args, **kw):
        t1=time.time()
        # 这是函数真正执行的地方
        func(*args, **kw)
        t2=time.time()
        # 计算下时长
        cost_time = t2-t1
        print("func cost:{}".format(cost_time))
    return wrapper

@timer
def add(x,y):
    print(x+y)

if __name__ == '__main__':
    add(3,5)

带参数的装饰器

def Log(deco_name):
    def logger(func):
        def wrapper(*args,**kw):
            print('Info from {}'.format(deco_name))
            print('{} is called...'.format(func.__name__))
            func(*args,**kw)
        return wrapper
    return logger

@Log('logger')
def add(x,y):
    print(x+y)

if __name__ == '__main__':
    add(3,5)

但其实这样的用的还是比较少。

查看结构,我们会发现,上面的实在太复杂了,如果我们不想实现那么多东西,改成下面这样。会实现什么呢?

def Logger(deco_name):
    def logger(func):
        print('Info from {}'.format(deco_name))
        print('func {} is registered...'.format(func.__name__))
        return func
    return logger

@Logger('logger')
def add(x,y):
    print(x+y)
    
if __name__ == '__main__':
    pass

这里我们什么都没调用,但是还是会有输出。

Info from logger
func add is registered...

原因就是,当我们加了参数,就证明我们实际是在调用Logger函数,即使我们没有调用add函数,Logger函数还是被认为调用了,上述做法可用于扫描到底我们的工程中,有哪些方法被定义了。得到一个方法定义的信息,我们可以将上面的程序稍微改改。

func_list=[]
def registry():
    def _registry(func):
        print('func {} is registered...'.format(func.__name__))
        func_list.append(func)
        return func
    return _registry

@registry()
def add(x,y):
    print(x+y)
    
if __name__ == '__main__':
    print(func_list)

输出:

func add is registered...
[<function add at 0x7f987660a0e0>]

是不是就对add函数,进行了一个注册,这样做能够对整个工程进行进行一个把握。

装饰类的装饰器

用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。

def singleton(cls):
    def get_instance(*args, **kw):
        print('class name:',cls.__name__)
        #判断是不是已经有了这个类的实例,如果有了,就直接返回实例
        if not cls.__name__ in instances:
            instance = cls(*args, **kw)
            instances[cls.__name__] = instance
        return instances[cls.__name__]
    return get_instance

@singleton
class User:
    _instance = None

    def __init__(self, name):
        self.name = name


if __name__ == '__main__':
    usr1=User('liuxin')
    print('user name:', usr1.name)
    usr2=User('liusiyu')
    print('user name:',usr2.name)

输出:

class name: User
user name: liuxin
class name: User
user name: liuxin

虽然我们在程序中实例化了两次,但是第二次的实例,根本就没有实例化成功,这就是单例模式,比如我们一个程序中的数据库连接对象,就应该是一个单例模式。

python自带装饰器

python有三种函数装饰器@staticmethod、@classmethod 和 @property,分别是修饰静态方法,类方法,属性方法(把方法当做属性)

@staticmemod

静态方法调用无需创建对象,用s类访问(现在已经不能用实例对象访问了)。但是静态方法不能访问类的成员方法和变量。

 class Student(object):
    __num=0
    def __new__(self):
        Student.__num+=1
    @classmethod
    def getNum(cls):
        return cls.__num
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self,value):
        if not isinstance(value,(int,float)):
            raise ValueError('score must be int or float')
        self.__score=value
    @staticmethod
    def get_class_name():
        return 'Student'
123456789101112131415161718

@classmethod

类方法用于一些脱离单个实例的操作或这属性修改,类方法可以用classsmethod修饰。classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
同时,由于python中不能利用重载来实例化对象,可以用类方法重载来进行对象的创建。

 class Student(object):
    __num=0
    def __new__(self):
        Student.__num+=1
    @classmethod
    def getNum(cls):#cls代表的是类本身,self代表的是实例对象本身
        return cls.__num
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self,value):
        if not isinstance(value,(int,float)):
            raise ValueError('score must be int or float')
        self.__score=value
123456789101112131415

实例对象不能访问声明为类方法的方法

stu=Student()
print(Student.getNum())
12

其中
print(stu.getNum())是非法的。

@property

对于class中的属性,为了不被非法修改,可以利用__property的方法进行私有化,然后利用set()和get()方法进行设置和访问,但是这样过于繁琐。可以利用属性进行设置:

class Student(object):
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self,value):
        if not isinstance(value,(int,float)):
            raise ValueError('score must be int or float')
        self.__score=value
123456789

运行

stu=Student()
stu.score=1  #设置
stu.score   #访问
123

关于更详细的@装饰器

装饰器的用法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值