tongsiying

阅读|运动|自律

0%

第27篇:装饰器

学习目标

掌握装饰器相关知识点

今天来学函数的第二个高阶应用,装饰器,装饰器属于Python的核心特色,学了这两个高阶功能,我们就可以做更多事情了。

装饰器是Python中用一个函数装饰另外一个函数或类并可以在不改变函数或方法的调用方式的情况下,为其提供额外功能的语法现象。装饰器本身是一个函数,只不过该函数可以具有特殊的含义,它的参数是被装饰的函数或类,它的返回值是一个带有装饰功能的函数。装饰器用来装饰函数或类使用装饰器可以在函数执行前和执行后添加相应操作**。换句话说,装饰器通过返回包装对象实现间接调用,以此在函数执行前或执行后插入额外逻辑。很显然,装饰器是一个高阶函数,它的参数和返回值都是函数。下面我们先通过一个简单的例子来说明装饰器的写法和作用。

一、案例引入

示例1

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

1
2
3
4
5
6
7
8
9
def say_hello():
print("hello!")

def say_goodbye():
print("hello!") # bug here

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

但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:

1
2
3
4
[DEBUG]: Enter say_hello()
Hello!
[DEBUG]: Enter say_goodbye()
Goodbye!

好,小A是个毕业生,他是这样实现的。

1
2
3
4
5
6
7
8
9
10
11
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()

很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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年以前),为一个函数添加额外功能的写法是这样的。

1
2
3
4
5
6
7
8
9
10
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) # 添加功能并保持原函数名不变

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

1
2
3
4
5
6
7
8
9
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def debug(func):
def wrapper(*args, **kwargs): # 指定宇宙无敌参数
print("[DEBUG]: enter {}()".format(func.__name__))
print('Prepare and say...',)
return func(*args, **kwargs)
return wrapper # 返回

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


if __name__ == '__main__':
say('你好啊')

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

1
2
3
4
5
6
7
8
9
10
def debug(func):
def wrapper(*args, **kwargs): # 指定宇宙无敌参数
print "[DEBUG]: enter {}()".format(func.__name__)
print 'Prepare and say...',
return func(*args, **kwargs)
return wrapper # 返回

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

至此,你已完全掌握初级的装饰器写法。

示例2:

假设已经有名为downlaodupload的两个函数,分别用于文件的上传和下载,下面的代码用休眠一段随机时间的方式模拟了下载和上传需要花费的时间,并没有联网做上传下载。

说明:用Python语言实现联网的上传下载也很简单,继续你的学习,这个环节很快就会来到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import random
import time


def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')


def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')


download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')

现在我们希望知道调用downloadupload函数做文件上传下载到底用了多少时间,这个应该如何实现呢?相信很多小伙伴已经想到了,我们可以在函数开始执行的时候记录一个时间,在函数调用结束后记录一个时间,两个时间相减就可以计算出下载或上传的时间,代码如下所示。

1
2
3
4
5
6
7
8
start = time.time()
download('MySQL从删库到跑路.avi')
end = time.time()
print(f'花费时间: {end - start:.3f}秒')
start = time.time()
upload('Python从入门到住院.pdf')
end = time.time()
print(f'花费时间: {end - start:.3f}秒')

通过上面的代码,我们可以得到下载和上传花费的时间,但不知道大家是否注意到,上面记录时间、计算和显示执行时间的代码都是重复代码。有编程经验的人都知道,重复的代码是万恶之源,那么有没有办法在不写重复代码的前提下,用一种简单优雅的方式记录下函数的执行时间呢?在Python中,装饰器就是解决这类问题的最佳选择。我们可以把记录函数执行时间的功能封装到一个装饰器中,在有需要的地方直接使用这个装饰器就可以了。

二、普通装饰器

将上文示例中的代码进行一下修改,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time


# 定义装饰器函数,它的参数是被装饰的函数或类
def record_time(func):

# 定义一个带装饰功能(记录被装饰函数的执行时间)的函数
# 因为不知道被装饰的函数有怎样的参数所以使用*args和**kwargs接收所有参数
# 在Python中函数可以嵌套的定义(函数中可以再定义函数)
def wrapper(*args, **kwargs):
# 在执行被装饰的函数之前记录开始时间
start = time.time()
# 执行被装饰的函数并获取返回值
result = func(*args, **kwargs)
# 在执行被装饰的函数之后记录结束时间
end = time.time()
# 计算和显示被装饰函数的执行时间
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
# 返回被装饰函数的返回值(装饰器通常不会改变被装饰函数的执行结果)
return result

# 返回带装饰功能的wrapper函数
return wrapper

使用上面的装饰器函数有两种方式,第一种方式就是直接调用装饰器函数,传入被装饰的函数并获得返回值,我们可以用这个返回值直接覆盖原来的函数,那么在调用时就已经获得了装饰器提供的额外的功能(记录执行时间),大家可以试试下面的代码就明白了。

1
2
3
4
download = record_time(download)
upload = record_time(upload)
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')

上面的代码中已经没有重复代码了,虽然写装饰器会花费一些心思,但是这是一个一劳永逸的骚操作,如果还有其他的函数也需要记录执行时间,按照上面的代码如法炮制即可。

在Python中,使用装饰器很有更为便捷的语法糖(编程语言中添加的某种语法,这种语法对语言的功能没有影响,但是使用更加方法,代码的可读性也更强),可以用@装饰器函数将装饰器函数直接放在被装饰的函数上,效果跟上面的代码相同,下面是完整的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import random
import time


def record_time(func):

def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
return result

return wrapper


@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')


@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')


download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')

上面的代码,我们通过装饰器语法糖为downloadupload函数添加了装饰器,这样调用downloadupload函数时,会记录下函数的执行时间。事实上,被装饰后的downloadupload函数是我们在装饰器record_time中返回的wrapper函数,调用它们其实就是在调用wrapper函数,所以拥有了记录函数执行时间的功能。

三、多个装饰器

多个装饰器同时修饰一个函数的的情况你能想到他们的执行顺序是什么样子的吗?看看下面的代码你就知道了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 定义函数: 完成包裹数据
def makeBold(fn):
def wrapped():
print("-----1-----")
return "<b>" + fn() + "</b>"

return wrapped


# 定义函数: 完成包裹数据
def makeItalic(fn):
def wrapped():
print("-------2-----")
return "<i>" + fn() + "</i>"

return wrapped


@makeBold # makeBold(makeItalic(test)
@makeItalic # # makeItalic(test
def test3():
print("-------3------")
return "hello world"

test()
"""
-----1-----
-------2-----
-------3------
<b><i>hello world</i></b>
"""

从上面的代码可以看出,多个装饰器同时修饰一个函数时,内函数执行顺序是从上往下(从最外层到最内层)以此执行。下面再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 定义函数: 完成包裹数据
def makeBold(fn):
print('进入 makeBold 装饰器 -1')
def wrapped():
print("makeBold开始装饰 1")
return "<b>" + fn() + "</b>"

return wrapped


# 定义函数: 完成包裹数据
def makeItalic(fn):
print('进入 makeItalic 装饰器 -2')
def wrapped():
print("makeItalic开始装饰 2")
return "<i>" + fn() + "</i>"

return wrapped

# 只要python解释器执行到了这个代码,那么就会自动的进行装饰,而不是等到调用的时候才装饰的
@makeBold
@makeItalic
def test():
print("执行test函数内容逻辑 - 3")
return "hello world"


print(test())

"""
进入 makeItalic 装饰器 -2
进入 makeBold 装饰器 -1
makeBold开始装饰 1
makeItalic开始装饰 2
执行test函数内容逻辑 - 3
<b><i>hello world</i></b>
"""

我们发现多个装饰器修饰一个函数时,多个装饰器外函数执行顺序和内函数执行顺序居然是相反的,外函数是从下往上(由内向外)顺序执行,这个是为什么呢?其实仔细想想,当Python解释器执行到@makeBold这一行代码时,会把其转化为makeBold(makeItalic(test)),两个装饰器函数会在这里执行,先会执行内层装饰器,后执行外层装饰器,而test()这个过程相当于开始执行装饰器返回值内函数,执行顺序从外层装饰器到内层装饰器。即多个装饰器的外函数在Python解释器执行到对应代码时会执行,内函数会有被装饰函数执行(加括号)时执行,执行顺序是外函数由内向外执行,内涵数由外向内执行。

四、被装饰函数的参数

那么装饰器修饰被装饰函数时,被装饰函数的参数如何处理呢?我们分三种情况来分析。

情况1:被装饰函数无参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time


def record_time(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

return wrapper


@record_time # 得到闭包函数record_time, func = record_time(func)
def func():
print('func do something!')
time.sleep(1)


func()
"""
func do something!
spend is 1.0044233798980713
"""

情况2:有指定个数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time

def record_time(func):
def wrapper(a, b):
start_time = time.time()
func(a, b)
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

return wrapper


@record_time # 得到闭包函数record_time, add = record_time(add)
def add(a, b):
print(a + b)
time.sleep(1)


add(3, 4)
"""
7
spend is 1.0194220542907715
"""

情况3:不定长参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time


def record_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

return wrapper


@record_time # 得到闭包函数record_time, add = record_time(add)
def add(*args):
num_sum = sum(num for num in args)
print(f'{args} sum is {num_sum}')
time.sleep(1)


add(1,2,3)
add(2,3,4,5)
"""
(1, 2, 3) sum is 6
spend is 1.0004258155822754
(2, 3, 4, 5) sum is 14
spend is 1.0004265308380127
"""

通常情况下,推荐大家用第三种写法。

五、被装饰函数带有返回值

当被装饰函数带有返回值的情况如何处理呢?下面的例子告诉你答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def outer(outertionName):
print("-----outer---1----")

def inner():
print("----inner---1--")
ret = outertionName()
print("---inner---2---")
return ret

print("----outer---2---")
return inner


@outer
def func():
print("---func----")
return "haha"


ret = func()
print(f"func return value is {ret}")

"""
-----outer---1----
----outer---2---
----inner---1--
---func----
---inner---2---
func return value is haha
"""

六、带有参数的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time

def time_logger(flag=0):
def record_time(func):
def wrapper(a, b):
start_time = time.time()
func(a, b)
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

if flag:
print('将此操作保留至日志')
return wrapper
return record_time


@time_logger(2) # 得到闭包函数record_time,add = record_time(add)
def add(a, b):
print(a + b)
time.sleep(1)


add(3, 4)
"""
将此操作保留至日志
7
spend is 1.0813958644866943
"""

七、类装饰器

1. 普通类装饰器

示例:记录一个函数的执行时间,用类装饰器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time


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

def __call__(self):
start_time = time.time()
self._func()
end_time = time.time()
print('spend is {}'.format(end_time - start_time))


@RecordTime # bar = RecordTime(bar)
def func():
print('func..')
time.sleep(2)


func()
"""
func..
spend is 2.0018553733825684
"""

2. 多个类装饰器

示例:在eat函数执行之前执行洗手,执行之后执行洗碗操作,用两个类装饰器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import time


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

def __call__(self):
print('wash hands ...')
return self._func()

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

def __call__(self):
ret = self._func()
print('wash the dishes ...')
return ret

@WashHands
@WashDishes
def eat():
print('start eating ...')
time.sleep(2)


eat()
"""
wash hands ...
start eating ...
wash the dishes ...
"""

3. 带参数的类装饰器

类装饰器函数本身也可以参数化,简单的说就是通过我们的装饰器也是可以通过调用者传入的参数来定制的,这个知识点我们在后面用上它的时候再为大家讲解。除了可以用函数来定义装饰器之外,通过定义类的方式也可以定义装饰器。如果一个类中有名为__call__的魔术方法,那么这个类的对象就可以像函数一样调用,这就意味着这个对象可以像装饰器一样工作,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import random
import time
from functools import wraps


class RecordTime:
def __init__(self, num=4):
self.num = num

def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {round(end - start, self.num)}秒')
return result

return wrapper


# 使用装饰器语法糖添加装饰器
@RecordTime(num=3)
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')


def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')


# 直接创建对象并调用对象传入被装饰的函数
upload = RecordTime()(upload)
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
"""
开始下载MySQL从删库到跑路.avi.
MySQL从删库到跑路.avi下载完成.
download执行时间: 2.001秒
开始上传Python从入门到住院.pdf.
Python从入门到住院.pdf上传完成.
upload执行时间: 5.0002秒
"""

上面的代码演示了两种添加装饰器的方式,由于RecordTime是一个类,所以需要先创建对象,才能把对象当成装饰器来使用,所以提醒大家注意RecordTime后面的圆括号,那是调用构造器创建对象的语法。如果为RecordTime类添加一个__init__方法,就可以实现对装饰器的参数化。使用装饰器还可以装饰一个类,为其提供额外的功能,这个知识点也等我们用到的时候再做讲解。

八、装饰器作用在类上

除了可以用在方法上,其实python的装饰器也可以作用于类上,在不改变类的情况下,给类增加一些额外的功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 下面是一个重写了特殊方法 __getattribute__ 的类装饰器,可以打印日志:
def log_getattribute(cls):
origin_getattribute = cls.__getattribute__

def new_getattribute(self, name):
print('greeting:', name)
return origin_getattribute(self, name)

cls.__getattribute__ = new_getattribute
return cls


# 应用
@log_getattribute
class A:
def __init__(self, x):
self.x = x

def spam(self):
pass


if __name__ == '__main__':
a = A('x')
print(a.x)
print(a.spam())
"""
greeting: x
x
greeting: spam
None
"""

九、保留原对象指定属性信息

以上书写装饰器的方式有什么问题吗?看下面一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time


def record_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

return wrapper

def func():
print('func ...')

print('decorator before name is ', func.__name__)

@record_time
def func():
print('func ...')

print('decorator after name is ', func.__name__)
"""
decorator before name is func
decorator after name is wrapper
"""

由此可以看出,装饰器会对原函数的元信息进行更改,可以使用wraps,进行原函数信息的添加

注解:wraps本身也是一个装饰器,他能把函数的元信息拷贝到装饰器函数中使得装饰器函数与原函数有一样的元信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
from functools import wraps

def record_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print('spend is {}'.format(end_time - start_time))

return wrapper

def func():
print('func ...')

print('decorator before name is ', func.__name__)

@record_time
def func():
print('func ...')

print('decorator after name is ', func.__name__)

"""
decorator before name is func
decorator after name is func
"""

十、取消装饰器的作用

如果希望取消装饰器的作用,那么在定义装饰器函数的时候,需要做一些额外的工作。Python标准库functools模块的wraps函数也是一个装饰器,我们将它放在wrapper函数上,这个装饰器可以帮我们保留被装饰之前的函数,这样在需要取消装饰器时,可以通过被装饰函数的__wrapped__属性获得被装饰之前的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import random
import time

from functools import wraps


def record_time(func):

@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.3f}秒')
return result

return wrapper


@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.randint(2, 6))
print(f'{filename}下载完成.')


@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.randint(4, 8))
print(f'{filename}上传完成.')


download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
# 取消装饰器
download.__wrapped__('MySQL必知必会.pdf')
upload = upload.__wrapped__
upload('Python从新手到大师.pdf')

十一、常用的内置装饰器

1.property

@property 可以把一个实例方法变成其同名属性,以支持实例访问,它返回的是一个property属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import math


class Circle:
def __init__(self, radius): # 圆的半径radius
self.radius = radius

@property
def area(self):
return math.pi * self.radius ** 2 # 计算面积

@property
def perimeter(self):
return 2 * math.pi * self.radius # 计算周长


# 我们可以通过实例访问到类中属性
circle = Circle(10)
print(circle.radius)

# 通过@property装饰后的方法也可以像访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值;
print(circle.area)
print(circle.perimeter)
"""
10
314.1592653589793
62.83185307179586
"""

一个property对象还具有setterdeleter可用作装饰器;setter是设置属性值。deleter用于删除属性值。而官方文档中给出了getter用于获取属性信息,但是实际使用中可以直接通过property获取属性信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class C:
def __init__(self):
self._x = None

@property
def x(self):
return self._x

@x.setter
def x(self, value):
self._x = value

@x.deleter
def x(self):
del self._x


# 实例化类
c = C()
# 为属性进行赋值
c.x = 100
# 输出属性值
print(c.x) # 100
# 删除属性
del c.x

总结:使用property:在设置属性时,可以对值对进检查,设置发生时,可以 修改设置的值,获取属性时,可以动态地计算值。

2.classmethod

修饰的方法不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A():
number = 10

@classmethod
def get_a(cls): #cls 接收的是当前类,类在使用时会将自身传入到类方法的第一个参数
print('这是类本身:',cls)# 如果子类调用,则传入的是子类
print('这是类属性:',cls.number)

class B(A):
number = 20
pass

# 调用类方法 不需要实例化可以执行调用类方法
A.get_a()
B.get_a()
"""
这是类本身: <class '__main__.A'>
这是类属性: 10
这是类本身: <class '__main__.B'>
这是类属性: 20
"""

3.staticmethod

@staticmethod:改变一个方法为静态方法,静态方法不需要传递隐性的第一参数,静态方法的本质类型就是一个函数 一个静态方法可以直接通过类进行调用,也可以通过实例进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import time

class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day

@staticmethod
def now(): # 用Date.now()的形式去产生实例,该实例用的是当前时间
t = time.localtime() # 获取结构化的时间格式
return Date(t.tm_year, t.tm_mon, t.tm_mday) # 新建实例并且返回

@staticmethod
def tomorrow(): # 用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
t = time.localtime(time.time() + 86400)
return Date(t.tm_year, t.tm_mon, t.tm_mday)


a = Date('1987', 11, 27) # 自己定义时间
print(a.year, a.month, a.day)
b = Date.now() # 采用当前时间
print(b.year, b.month, b.day)
c = Date.tomorrow() # 采用明天的时间
print(c.year, c.month, c.day)

"""
1987 11 27
2020 12 17
2020 12 18
"""

十二、简单的总结

闭包和装饰器是Python中的特色语法,装饰器是闭包可以通过装饰器来增强现有的类或函数,这是一种非常有用的编程技巧。

赞赏一下吧~