Skip to content

复杂装饰器

多个装饰器

装饰器只起到装饰作用,所以可以多个装饰器一起使用。

python
"""
在看电影的时候,不仅要过滤年龄,还需要过滤性别
"""


class User:
    """用户模型"""

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

    def __str__(self):
        return self.name


def filter_age_and_gender(func):
    def wrapper(*args, **kwargs):
        print(args, kwargs)
        user = kwargs['user']
        if user.age < 18 or user.gender != '女':
            raise Exception('%s 不能看电影' % user)
        ret = func(*args, **kwargs)
        return ret

    return wrapper


def filter_age(func):
    def wrapper(*args, **kwargs):
        print('filter_age')
        user = kwargs['user']
        if user.age < 18:
            raise Exception('%s 不能看电影' % user)
        ret = func(*args, **kwargs)
        return ret

    return wrapper


def filter_gender(func):
    def wrapper(*args, **kwargs):
        print('filter_gender')
        user = kwargs['user']
        if user.gender != '女':
            raise Exception('%s 不能看电影' % user)
        ret = func(*args, **kwargs)
        return ret

    return wrapper


# 过滤 18 岁以上的女生

# @filter_age_and_gender
@filter_age  # 先执行上面的,再执行下面的
@filter_gender
def watch_movie(user=None):
    print("%s 正在观看电影" % user)


user1 = User('张三', 17, '女')
user2 = User('李四', 18, '男')
try:
    watch_movie(user=user1)
except Exception as e:
    print(e)
try:
    watch_movie(user=user2)
except Exception as e:
    print(e)

"""
    面向过程编程   oop: 面向对象   aop:面向切片编程

"""

带参数的装饰器

我们知道下面两种代码是等价的:

python
@dec
def func(...):
    ...
python
func = dec(func)

我们可以把它当成是纯文本的替换,于是可以是这样的:

python
@dec(arg)
def func(...):
    ...
python
func = dec(arg)(func)

这也就是我们看到的 带参数 的装饰器。可见,只要 dec(arg) 的返回值满足“装饰器”的定义即可。(接受一个函数,并返回一个新的函数)

案例:

python
"""
在看电影的时候,需要过滤会员等级
"""


class User:
    """用户模型"""

    def __init__(self, name, age, gender, level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level

    def __str__(self):
        return self.name


def watch_movie(user=None):
    print("%s 正在观看电影" % user)


user1 = User('张三', 17, '女', 3)
user2 = User('李四', 18, '男', 4)
try:
    watch_movie(user=user1)
except Exception as e:
    print(e)
try:
    watch_movie(user=user2)
except Exception as e:
    print(e)
参考答案
python
class User:
    """用户模型"""

    def __init__(self, name, age, gender, level):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level

    def __str__(self):
        return self.name


def filter_level_3(func):
    def wrapper(*args, **kwargs):
        user = kwargs['user']
        if user.level < 3:
            raise Exception('%s 不能看电影' % user)
        ret = func(*args, **kwargs)
        return ret

    return wrapper


def filter_level_4(func):
    def wrapper(*args, **kwargs):
        user = kwargs['user']
        if user.level < 4:
            raise Exception('%s 不能看电影' % user)
        ret = func(*args, **kwargs)
        return ret

    return wrapper


def filter_level(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            user = kwargs['user']
            if user.level < level:
                raise Exception('%s 不能看电影' % user)
            ret = func(*args, **kwargs)
            return ret

        return wrapper

    return decorator


def filter_level_three():  # 对于经常过滤的参数,可以再封装一个函数
    return filter_level(3)


# 系统里面的等级是从 1 - 10
# @filter_level_3
# @filter_level(3)
@filter_level_three()
def watch_movie(user=None):
    print("%s 正在观看电影" % user)


user1 = User('张三', 17, '女', 3)
user2 = User('李四', 18, '男', 4)
try:
    watch_movie(user=user1)
except Exception as e:
    print(e)
try:
    watch_movie(user=user2)
except Exception as e:
    print(e)

类作为装饰器

如果说 Python 里一切都是对象的话,那函数怎么表示成对象呢?其实只需要一个类实现 __call__ 方法即可。

python
import time


class Timer:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        before = time.time()

        result = self._func(*args, **kwargs)

        after = time.time()
        print("运行时间:", after - before)

        return result


@Timer
def add(x, y=10):
    return x + y

也就是说把类的构造函数当成了一个装饰器,它接受一个函数作为参数,并返回了一个对象,而由于对象实现了 __call__ 方法,因此返回的对象相当于返回了一个函数。因此该类的构造函数就是一个装饰器。

小结

装饰器中还有一些其它的话题,例如装饰器中元信息的丢失,如何在类及类的方法上使用装饰器等。但本文里我们主要目的是简单介绍装饰器的原因及一般的使用方法,能用上的地方就大胆地用上吧!