15

发布时间 2023-03-30 20:59:03作者: 一袋林檎

装饰器

装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。

为什么需要装饰器

我们假设你的程序实现了say_hello()say_goodbye()两个函数。

def say_hello():
    print("hello!")


def say_goodbye():
    print("hello!")  # 此处应打印goodbye


if __name__ == '__main__':
    say_hello()
    say_goodbye()

假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。

[DEBUG]: Enter say_hello()
Hello!

[DEBUG]: Enter say_goodbye()
Goodbye!

实现方式:

def say_hello():
    print("[DEBUG]: enter say_hello()")
    print("hello!") 
 
def say_goodbye():
    print("[DEBUG]: enter say_goodbye()")
    print("hello!")
 
if __name__ == '__main__':
    say_hello()
    say_goodbye()

对上述代码进行优化:

def debug():
    import inspect
    caller_name = inspect.stack()[1][3]  # 可以返回函数名与当前函数的返回值
    print("[DEBUG]: enter {}()".format(caller_name))


def say_hello():
    debug()
    print("hello!")


def say_goodbye():
    debug()
    print("goodbye!")


if __name__ == '__main__':
    say_hello()
    say_goodbye()

上述代码需要在每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能

如何实现一个装饰器

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()
    return wrapper


def say_hello():
    print("hello!")


say_hello = debug(say_hello)

say_hello()

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func):
    def wrapper():
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func()

    return wrapper


@debug
def say_hello():
    print("hello!")


say_hello()

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):
    def wrapper(something):  # 指定一毛一样的参数
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func(something)

    return wrapper  # 返回包装过函数


@debug
def say(something):
    print("hello {}!".format(something))


say('顾安')

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

def debug(func):
    def wrapper(*args, **kwargs):
        print("[DEBUG]: enter {}()".format(func.__name__))
        return func(*args, **kwargs)
    return wrapper


@debug
def say(something):
    print("hello {}!".format(something))


say('顾安')
带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。

def logging(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                level=level,
                func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper


@logging(level='INFO')
def say(something):
    print("say {}!".format(something))


# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)

@logging(level='DEBUG')
def do(something):
    print("do {}...".format(something))


if __name__ == '__main__':
    say('hello')
    do("my work")

是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只要它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

基于类的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

class Test:
    def __call__(self):
        print('call me!')


t = Test()
t()  # call me

__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class Logging:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[DEBUG]: enter function {func}()".format(
            func=self.func.__name__))
        return self.func(*args, **kwargs)


@Logging
def say(something):
    print("say {}!".format(something))


say('木木')
带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接收一个函数并返回一个函数。

class Logging:
    def __init__(self, level='INFO'):
        self.level = level

    def __call__(self, func):  # 接收函数
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                level=self.level,
                func=func.__name__))
            func(*args, **kwargs)

        return wrapper  # 返回函数


@Logging(level='INFO')
def say(something):
    print("say {}!".format(something))


say('木木')

property属性

@property - 简介

什么是property属性?

一种用起来像是使用的实例属性一样的特殊属性,可以对应于某个方法。

class Foo:
    def func(self):
        pass

    # 定义property属性
    @property
    def prop(self):
        pass
      
foo_obj = Foo()
foo_obj.func()  # 调用实例方法
foo_obj.prop  # 调用property属性
class Goods:
    @property
    def money(self):
        return 100


goods = Goods()
print(goods.money)

property属性的定义和调用要注意一下几点:

  • 定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
  • 调用时,无需括号
简单的示例

对于京东商城中显示电脑主机的列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据 这个分页的功能包括:

  • 根据用户请求的当前页和总数据条数计算出 m 和 n
  • 根据m 和 n 去数据库中请求数据
class Pager:
    def __init__(self, current_page):
        # 用户当前请求的页码(第一页、第二页...)
        self.current_page = current_page
        # 每页默认显示10条数据
        self.per_items = 10

    @property
    def start(self):
        val = (self.current_page - 1) * self.per_items
        return val

    @property
    def end(self):
        val = self.current_page * self.per_items
        return val


# ############### 调用 ###############
p = Pager(1)
print(p.start)  # 就是起始值,即:m
print(p.end)  # 就是结束值,即:n
  • Python的property属性的功能是:property属性内部进行一系列的逻辑计算,最终将计算结果返回。
property属性的两种方式
  • 装饰器 即:在方法上应用装饰器
  • 类属性 即:在类中定义值为property对象的类属性

装饰器方式

在类的实例方法上应用@property装饰器

Python中的类有经典类新式类新式类的属性比经典类的属性丰富。( 如果类继object,那么该类是新式类 )

经典类,具有一种@property装饰器:

class Goods:
    @property
    def price(self):
        return 100
      
      
obj = Goods()
result = obj.price  # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
print(result)

新式类,具有三种@property装饰器

class Goods(object):
    @property
    def price(self):
        print('@property')

    @price.setter
    def price(self, value):
        print('@price.setter')

    @price.deleter
    def price(self):
        print('@price.deleter')


obj = Goods()
print(obj.price)   # 自动执行 @property 修饰的 price 方法,并获取方法的返回值
obj.price = 123    # 自动执行 @price.setter 修饰的 price 方法,并将  123 赋值给方法的参数
del obj.price      # 自动执行 @price.deleter 修饰的 price 方法

注意:

  • 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
  • 新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法

由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
print(obj.price)  # 获取商品价格
obj.price = 200  # 修改商品原价
print(obj.price)
del obj.price  # 删除商品原价
# 当前属性被删除之后再获取则报错
# print(obj.price)

类属性方式,创建值为property对象的类属性

  • 当使用类属性的方式创建property属性时,经典类新式类无区别
class Goods:
    def get_price(self):
        return 100

    price = property(get_price)


obj = Goods()
result = obj.price  # 自动调用get_price方法,并获取方法的返回值
print(result)

property方法中有个四个参数

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性.__doc__ ,此参数是该属性的描述信息
class Foo(object):
    def get_bar(self):
        print("getter...")
        return 'a...'

    def set_bar(self, value):
        """必须两个参数"""
        print("setter:", value)
        return 'set value' + value

    def del_bar(self):
        print("deleter...")
        return 'b...'

    BAR = property(get_bar, set_bar, del_bar, "description...")


obj = Foo()

print(obj.BAR)  # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "c"  # 自动调用第二个参数中定义的方法:set_bar方法,并将“c”当作参数传入
desc = Foo.BAR.__doc__  # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR  # 自动调用第三个参数中定义的方法:del_bar方法

由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

class Goods(object):

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    def get_price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    def set_price(self, value):
        self.original_price = value

    def del_price(self):
        del self.original_price

    PRICE = property(get_price, set_price, del_price, '价格属性描述...')


obj = Goods()
print(obj.PRICE)  # 获取商品价格
obj.PRICE = 200  # 修改商品原价
print(obj.PRICE)
del obj.PRICE  # 删除商品原价
@property - 应用
  • 私有属性添加getter和setter方法
class Money(object):
    def __init__(self):
        self.__money = 0

    def get_money(self):
        return self.__money

    def set_money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")


money = Money()
print(money.get_money())
money.set_money(10)
print(money.get_money())
  • 使用property升级getter和setter方法
class Money(object):
    def __init__(self):
        self.__money = 0

    def get_money(self):
        return self.__money

    def set_money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")

    # 定义一个属性,当对这个money设置值时调用setMoney,当获取值时调用getMoney
    money = property(get_money, set_money)


money_obj = Money()
money_obj.money = 100  # 调用setMoney方法
print(money_obj.money)  # 调用getMoney方法
  • 使用property取代getter和setter方法
    • 重新实现一个属性的设置和读取方法,可做边界判定
class Money(object):
    def __init__(self):
        self.__money = 0

    # 使用装饰器对money进行装饰,那么会自动添加一个叫money的属性,当调用获取money的值时,调用装饰的方法
    @property
    def money(self):
        return self.__money

    # 使用装饰器对money进行装饰,当对money设置值时,调用装饰的方法
    @money.setter
    def money(self, value):
        if isinstance(value, int):
            self.__money = value
        else:
            print("error:不是整型数字")


money_obj = Money()
money_obj.money = 100
print(money_obj.money)