这一篇教程的内容是装饰器(Decorators)的使用。

先忘掉“装饰器”这三个字。(你刚才不说不就完了吗…)

我们先来看一些代码,这些代码是分别获取当前系统时间的时、分、秒。

示例代码:


import time

def get_hours():
    return time.localtime()[3]

def get_minutes():
    return time.localtime()[4]

def get_seconds():
    return time.localtime()[5]

print(get_hours(), get_minutes(), get_seconds(), sep=':') # 显示输出结果类似:12:49:2


上方代码中,用到了time模块,通过localtime()函数,我们能够获取到本地时间的元组。

在本地时间的元组中包含的元素依次为:年,月,日,时,分,秒,星期几,本年第几天,是否夏令时。

所以,大家能够看到,在上方代码中,通过元组中元素的编号3、4、5获取到了时、分、秒的数值。

不过,当时、分、秒的数值小于10的时候,都是1位数字(0:0:0),如果想得到两位数字(00:00:00)怎么办呢?

当然,我们可以修改每一个写好的函数;但是,这样有点笨。

而且,不一定哪一天,你又想要1位数字,还要全部改回来。

最好的办法应该是再写一个函数,用这个函数处理时、分、秒这三个函数的返回值。

接下来,我们加入一段代码。

示例代码:


def new_time(fun):  # 定义处理时间的函数,对传入函数的返回值进行再处理。
    return ('0' + str(fun()))[-2:]  # 返回处理结果

print(new_time(get_hours), new_time(get_minutes), new_time(get_seconds), sep=':')  # 显示输出结果类似:12:49:02


在上方代码中,我们把函数作为参数传入新写的函数,然后对传入函数的返回值进行处理,将处理后的值返回。

这样做效果上没有问题。

但是,大家注意print语句,参数全部和刚才不一样了。

那么大家想一下,如果是之前写好的函数,也在被很多客户代码(使用这个函数的代码)使用,通过这样处理的话,大量的客户代码都需要修改,这样还不如修改写好的这几个函数。

那么,能不能既不修改写好的函数,也不修改客户代码,来解决这个问题呢?

大家是否还记得之前学过的闭包?

简单来说,闭包是函数里面嵌套函数,外层函数返回值为内层函数。

我们把刚才新增的代码修改一下。

示例代码:


def format_time(fun): # 函数传入 def new_time(): # 定义处理函数返回值的函数,注意,参数由外层函数获取,这个函数不需要参数。 return ('0' + str(fun()))[-2:] # 返回处理结果 return new_time # 闭包,返回处理函数的函数。


上面这段代码中,外层函数负责返回内嵌函数,内嵌函数负责对外层函数的参数进行处理,返回新的值。

这实际上就是传入一个函数,处理函数的返回值,再传出一个函数。

这和我们想要的解决方案已经很相像了。

以秒的函数为例,我们希望把这个获取1位秒数返回值的函数,变成获取双位秒数返回值的函数。

那么,这段代码怎么用呢?

示例代码:(错误示例)


print(format_time(get_hours)(), format_time(get_minutes)(), format_time(get_seconds)(), sep=':')


这特么有点尴尬,跟刚才没什么区别,还是要修改所有客户代码。

打个比方,我们和女朋友约会。

我们肯定希望女朋友化完妆再约会,而不是约会时候还要化妆。

刚才的代码,就很像这个场景。

format_time()函数就是化妆,参数fun是自己的女朋友。

我们希望print语句中只有format_time()函数中的参数,而不希望看到format_time()函数的出现。

实际上,format_time()函数就是一个装饰器。

我们怎么能够让它不出现,还能够得到它的装饰效果?

我们使用装饰符“@”。

示例代码:


import time

def format_time(fun):  # 装饰器
    def new_time():
        return ('0' + str(fun()))[-2:]
    return new_time

@format_time  # 为函数指定装饰器
def get_hours():
    return time.localtime()[3]

@format_time  # 为函数指定装饰器
def get_minutes():
    return time.localtime()[4]

@format_time  # 为函数指定装饰器
def get_seconds():
    return time.localtime()[5]

print(get_hours(), get_minutes(), get_seconds(), sep=':')


上方代码中,时、分、秒的函数之前都加了一句“@format_time”。

这就是声明在调用这个函数的时候,要使用哪个装饰器进行处理,并得到处理后的结果。

是不是很简单,就像约会之前@自己的女朋友(被装饰的函数)化妆(装饰器)后再来。

另外,装饰器也能够而外接收参数。

我们再来看个例子,对计算合计的函数添加货币符号。


def add_symbol(symbol):  # 获取装饰器参数
    def dec_function(fun):  # 被装饰函数传入
        def new_total(price, count):  # 获取被装饰函数的参数
            return symbol + str(fun(price, count))  # 对被装饰函数进行处理,并返回结果。
        return new_total  # 返回装饰后的函数
    return dec_function  # 返回装饰后的函数

@add_symbol('¥')
def total(price, count):
    return price * count

print(total(2.5, 3))  # 显示输出结果为:¥7.5


在上方代码中,大家能够看到,装饰器的嵌套函数变成了三层。

最外层的函数是用于接收货币符号的参数,中间层函数用于接收被装饰的函数,最内层函数用于装饰处理。

这里要注意,最内层函数的参数与被装饰函数的参数一致。

以上是关于自定义的装饰器。

在Python中,也有内置的装饰器,例如,我们之前接触的属性property。

 这个操作。

示例代码:


@property  # 将方法装饰为属性
def cubage(self):
    return self.length, self.width, self.height

@cubage.setter  # 将方法装饰为cubage属性的setter方法
def cubage(self, tup):
    if len([i for i in tup if isinstance(i, (int, float))]) == 3:
        self.length, self.width, self.height = tup 
    else:
        raise TypeError


除了@property 这个装饰器,还有两个内置的装饰器@staticmethod和@classmethod。

先来看一段代码。


class MyClass:
    def sm():  # 静态方法没有self参数
        print('静态方法...')

    def cm(cls):  # 类成员方法带有cls参数
        print('类成员方法...')

MyClass.sm()  # 类直接调用静态方法,显示输出结果:静态方法...
MyClass().sm()  # 类的实例调用静态方法,抛出异常,无法调用。
MyClass.cm()  # 类直接调用类成员方法,抛出异常,无法调用。
MyClass().cm()  # 类的实例调用类成员方法,显示输出结果:类成员方法...


为上方代码中的两个方法指定装饰器。


class MyClass: @staticmethod def sm(): # 静态方法没有self参数 print('静态方法...') @classmethod def cm(cls): # 类成员方法带有cls参数 print('类成员方法...')


通过指定装饰器,静态方法和类成员方法就都能够被类和实例访问了。

本节知识点:

1、装饰器

2、静态方法与类成员方法

本节英文单词与中文释义:

1、decorator:装饰器

2、hour:小时

3、minute:分钟

4、second:秒

5、static:静态

练习:

根据下方代码编写装饰器。


@add_symbol('¥')
def total(price, count):
    return price * count

@add_symbol('$',6.5) # 非人民币结算时,除以总计除以汇率。
def us_total(price, count):
    return price * count

print(total(2.5, 3))  # 显示输出结果为:¥7.5
print(us_total(2.5,3))  # 显示输出结果为:$1.15