一、装饰器的定义
装饰器,顾名思义,就是起到装饰的作用,即在不改变已有函数代码及其调用方式的前提下,对已有函数进行功能扩展,实现了低侵入性、高内聚低耦合的目标。
二、装饰器使用的前置知识
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()