Python中装饰器的介绍与使用
一、装饰器的定义
装饰器,顾名思义,就是起到装饰的作用,即在不改变已有函数代码及其调用方式的前提下,对已有函数进行功能扩展,实现了低侵入性、高内聚低耦合的目标。
二、装饰器使用的前置知识
2.1 Python中函数的使用
和其它编程语言不一样,Python中的函数可以一次性返回多个值(tuple),而且函数可以被引用,赋值给其它变量甚至是函数名。
def hello(): print("hello") def world(): print("world") # 函数名可以重新被赋值,导致函数名和函数内容的置换 hello,world = world, hello # world hello() # hello world()
在Python中,函数是一等公民,可以像普通变量一样被赋值、被引用、被当作其它函数的入参、被当作其它函数的返回值。
def say_hello(name): print(f"Hello, {name}") def say_hi(name): print(f"Hi, {name}") def greet_job(func, name): func(name) # 函数被当作其它函数的入参 # Hello, Jack greet_job(say_hello, "Jack") # Hi, Jack greet_job(say_hi, "Jack")
2.2 Python中的内部函数
Python允许在一个函数中定义另外一个函数,即实现函数的嵌套,内部函数具有如下的特性:
能够访问所有外层函数中的所有资源;
该内部函数仅在其直接外层函数中可见;
def parent(): print("parent函数执行") local_var = "zhangsan" def first_child(): print(f"first_child函数执行,可以访问到外层函数的资源:{local_var}") def second_child(): print(f"second_child函数执行,可以访问到外层函数的资源:{local_var}") # 内部函数仅在其直接外层函数中可见 first_child() second_child() parent()
三、装饰器的使用
3.1 简单装饰器的使用
# 定义装饰器,该装饰器什么也不做,把入参中的函数原封不动地返回 def my_decorator(func): return func def say_hello(): print("Hello") # 使用装饰器装饰一下原函数 say_hello = my_decorator(say_hello) say_hello()
如上是最简单的一个例子,装饰器什么也没做,只是把入参的函数原封不动地返回,一般而言,装饰器肯定会有所内容地:
def my_decorator(func): print("装饰器函数被调用了") # 对原函数进行装饰 def wrapper(): print(f"在{func.__name__}之前做点事情...") func() print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper def say_hello(): print("Hello") say_hello = my_decorator(say_hello) say_hello()
输出内容如下:
装饰器函数被调用了 在say_hello之前做点事情... Hello 在say_hello之后做点事情...
3.2 使用装饰器语法糖
通过如上装饰器的简单使用,我们其实知道了其本质,就是把被装饰的原函数当作参数传入装饰器,然后对其装饰一番,再返回赋值给原函数的引用,从而在原函数执行的时候,顺带执行装饰的代码。
然后如上的写法不够简洁,不够优雅,所以Python为我们提供了语法糖写法:
def my_decorator(func): print("装饰器函数被调用了") # 对原函数进行装饰 def wrapper(): print(f"在{func.__name__}之前做点事情...") func() print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper @my_decorator def say_hello(): print("Hello") say_hello()
其实就是使用@@my_decorator替换了一句say_hello = my_decorator(say_hello),但确实看上去简洁优雅多了。
3.3 装饰器的内部函数
我们在上面的例子中,装饰器内部又定义了一个wrapper函数,那么装饰逻辑分布在wrapper的外部和在内部的区别是什么呢?
其实我们最终返回的是对原函数装饰过后的wrapper函数,所以:
在wrapper内部的装饰逻辑,只会在原函数调用的时候才会执行;
在wrapper外部的装饰逻辑,只要装饰器生效就会执行;
def my_decorator(func): # 无论原函数执行与否,只要装饰器使用了,就会被执行 print("装饰器函数被调用了") # 对原函数进行装饰 # 只有当原函数执行时,内部的装饰逻辑才会执行 def wrapper(): print(f"在{func.__name__}之前做点事情...") func() print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper
3.4 带参数的原函数
为了能在装饰器中给原函数传入参数,我们不得不在定义wrapper内部函数的时候,增加一个入参,如下所示:
def my_decorator(func): print("装饰器函数被调用了") # 对原函数进行装饰 def wrapper(name): print(f"在{func.__name__}之前做点事情...") func(name) print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper @my_decorator def say_hello(name): print(f"Hello,{name}") say_hello("Jack")
但是这样有一个缺点,这个装饰器几乎只对这个方法有用,对其它方法,如果参数不匹配的话就没法使用了,所以我们通常结合Python中的变长参数一起使用:
def my_decorator(func): print("装饰器函数被调用了") # 对原函数进行装饰,*args表示多个顺序参数,**kwargs表示多个字典参数 def wrapper(*args, **kwargs): print(f"在{func.__name__}之前做点事情...") func(*args, **kwargs) print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper @my_decorator def say_hello(name, age=10): print(f"Hello,{name}, your age is: {age}") say_hello("Jack", 12)
这样改造之后,其它任意函数都可以使用我们的装饰器了。
3.5 带参数的装饰器
我们在如上案例中使用装饰器时,装饰器并没有入参,其实Python也是支持的,只不过需要将装饰器再封装一层:
def my_repeat(times): def my_decorator(func): def wrapper(*args, **kwargs): print(f"在{func.__name__}之前做点事情...") # 闭包函数,使用了其外层函数的资源times for n in range(times): func(*args, **kwargs) print(f"在{func.__name__}之后做点事情...") # 将装饰后的函数返回给原函数的引用,更新原函数 return wrapper return my_decorator @my_repeat(3) def say_hello(name, age=10): print(f"Hello,{name}, your age is: {age}") # 原函数调用时,其装饰函数外层的函数上下文和资源仍然存在 say_hello("Jack", 12) 输出结果为: 在say_hello之前做点事情... Hello,Jack, your age is: 12 Hello,Jack, your age is: 12 Hello,Jack, your age is: 12 在say_hello之后做点事情... 其实@@my_repeat(3)就等价于: my_decorator = my_repeat(3) say_hello = my_decorator(say_hello)
3.6 装饰器装饰类
在上面的使用中,我们都是用来装饰函数,那么装饰类是什么意思,如何使用呢?
相较于函数装饰器,类装饰器具有灵活度大、高内聚、封装性的优点,此时主要依靠类的__call__方法来实现对原函数的装饰逻辑。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print(f"在{self._func.__name__}之前做点事情") self._func() print(f"在{self._func.__name__}之后做点事情") @Foo def bar(): print("原函数执行的逻辑....") bar()
此时的@Foo就等价于bar = Foo(bar);
3.7 装饰器的执行顺序
当有多个装饰器装饰同一个函数的时候,各个装饰器的执行顺序会是怎么样的呢?
def my_decorator1(func): def wrapper(*args, **kwargs): print(f"my_decorator1在{func.__name__}之前做点事情...") func(*args, **kwargs) print(f"my_decorator1在{func.__name__}之后做点事情...") return wrapper def my_decorator2(func): def wrapper(*args, **kwargs): print(f"my_decorator2在{func.__name__}之前做点事情...") func(*args, **kwargs) print(f"my_decorator2在{func.__name__}之后做点事情...") return wrapper def my_decorator3(func): def wrapper(*args, **kwargs): print(f"my_decorator3在{func.__name__}之前做点事情...") func(*args, **kwargs) print(f"my_decorator3在{func.__name__}之后做点事情...") return wrapper @my_decorator1 @my_decorator2 @my_decorator3 def say_hello(name, age=10): print(f"Hello,{name}, your age is: {age}") say_hello("Jack", 12) 输出结果如下: my_decorator1在wrapper之前做点事情... my_decorator2在wrapper之前做点事情... my_decorator3在say_hello之前做点事情... Hello,Jack, your age is: 12 my_decorator3在say_hello之后做点事情... my_decorator2在wrapper之后做点事情... my_decorator1在wrapper之后做点事情...
四、装饰器的应用
Python自带了三个原生的装饰器,下面逐一进行介绍。
4.1 @property
当我们需要访问对象中的属性的时候,对于公开的属性,可以直接访问,对于私有的属性,可以通过get和set方法来访问,如下所示:
class Person(object): def __init__(self, name, age): self.name = name self._age = age # _age是私有属性,必须通过方法来访问 def get_age(self): return self._age # _age是私有属性,必须通过方法来修改 def set_age(self, age): self._age = age def __call__(self): # 对象内部可以不受限制地访问自身的属性 print('姓名:{},年龄:{}'.format(self.name,self._age)) jack = Person("jack", 20) print(f"jack的姓名为:{jack.name},年龄为:{jack.get_age()}") jack.set_age(35) print(f"jack的姓名为:{jack.name},年龄为:{jack.get_age()}") jack()
输出内容为:
jack的姓名为:jack,年龄为:20 jack的姓名为:jack,年龄为:35
姓名:jack,年龄:35
但是每次需要访问对象的私有属性,都要通过set和get有点麻烦,所以Python提供了@property来将这些方法变成可以直接读写的属性:
class Person(object): def __init__(self, name, age): self.name = name self._age = age @property def age(self): return self._age def set_age(self, age): self._age = age def __call__(self): print('姓名:{},年龄:{}'.format(self.name,self._age)) jack = Person("jack", 20) # 可以以读取属性的方式来调用对象的方法 print(f"jack的姓名为:{jack.name},年龄为:{jack.age}") jack.set_age(35) print(f"jack的姓名为:{jack.name},年龄为:{jack.age}") jack()
如此,我们既可以维持原来保护私有属性的原则,又可以像读取公开属性一样获取其值。
4.2 @classmethod
一般情况下,如果我们想调用某个类的方法的话,首先需要实例化一个对象,然后使用对象来调用这个方法:
class Animal(object): name = "tom" def eat(self): print(f"{self.name}正在吃东西") def sleep(self): print(f"{self.name}正在睡觉") def make_noise(cls): print("miao~miao~") cat = Animal() cat.eat() cat.sleep() cat.make_noise()
但是使用@classmethod就能让我们可以直接通过类名来调用方法,不需要实例化类的对象了:
class Animal(object): name = "tom" @classmethod def eat(cls): print(f"{cls.name}正在吃东西") @classmethod def sleep(cls): print(f"{cls.name}正在睡觉") @classmethod def make_noise(cls): print("miao~miao~") # 完全不需要实例化对象,直接可以调用方法 Animal.eat() Animal.sleep() Animal.make_noise() 4.3 @staticmethod @staticmethod的用法和@classmethod的用法基本类似,区别是被装饰的原函数中不需要cls入参。 class Animal(object): name = "tom" @staticmethod def eat(): print(f"{Animal.name}正在吃东西") @classmethod def sleep(cls): print(f"{cls.name}正在睡觉") @classmethod def make_noise(cls): print("miao~miao~") Animal.eat() Animal.sleep() Animal.make_noise()