tyltr技术窝

1. 装饰器的应用#

python常用的装饰器:

  • 静态方法装饰器 @staticmethod
  • 类方法装饰器 @classmethod
  • @property
  • @click 第三方装饰器,快速创建命令行。使用 pip install click进行安装

1.1 click模块#

click使用分为两步骤:

  • 1.使用 @click.command()装饰一个函数,使之成为命令行接口;
  • 2.使用 @click.option() 等装饰函数,为其添加命令行选项等。

示例#

文件名: cmd.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import click


@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
for x in range(count):
click.echo('Hello %s!' % name)


if __name__ == '__main__':
hello()
获取命令行的说明#
1
2
# help
python cmd.py --help

输出:

1
2
3
4
5
6
Usage: cmd.py [OPTIONS]

Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
运行#
1
python  cmd.py --count 2  --name tyltr

输出:

1
2
Hello tyltr!
Hello tyltr!

2.函数装饰器#

装饰器的原理:python中一切皆对象,函数也是对象。把函数(不妨称为函数A)作为作为另一个函数(不妨称为函数B)的参数,即可实现扩展B的功能

2.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
26
27
28
import time


def statime(func):
"""
统计运行时间
:param func: 装饰的函数
:return:
"""

def inner(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print(time.time() - start)
return res
return inner


@statime
def add(x,y):
time.sleep(1)
return x+y



if __name__ == '__main__':
a = add(1,2)
print(a)

输出:

1
2
1.0026731491088867
3

2.2 校验参数#

有参数的装饰器

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 functools


def check_args(type_): # 带参数的装饰器,传入参数。对应

def wrapper(func): # 传入函数引用
@functools.wraps(func) # “修复”函数名
def inner(args): # 传入函数的参数
# 判断类型
if not isinstance(args, type_):
raise Exception("类型有误")
return func(args)

return inner

return wrapper


@check_args(int)
def div(args):
return args / 2


if __name__ == '__main__':
t = div(4)
print(t)

上例demo 可以实现参数的校验。

3. 类装饰器#

大部分装饰器都基于函数和闭包实现的。

3.1 类调用#

实现了魔法方法 __call__ 的对象,可以像函数一样被调用。

示例#

1
2
3
4
5
6
7
8
9
class Foo(object):
def __call__(self, *args, **kwargs):
print('qqq')


if __name__ == '__main__':
f = Foo()
if callable(f):
f()

运行结果:

1
qqq

说明:
callable(obj) 判断是否是可调用的类型。如果可调用,则返回 True;否则 False
可调用对象:函数、方法、实现了__call__ 的对象。
示例:

1
2
3
4
5
6
7

# 函数,可调用
def p():
print("pppp")

if __name__ == '__main__':
print(callable(p))

输出 : True

3.2 实现类装饰器#

实现一个装饰器:在每次函数执行前都会等到 duration 秒。

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
import time
import functools


class DelayFunc:
def __init__(self, duration, func):

self.duration = duration
self.func = func

def __call__(self, *args, **kwargs):
print(f'Wait for {self.duration} seconds...')
time.sleep(self.duration)
return self.func(*args, **kwargs)

def eager_call(self, *args, **kwargs):
return self.func(*args, **kwargs)


def delay(duration):
"""装饰器:推迟某个函数的执行。同时提供 .eager_call 方法立即执行
"""
# 此处为了避免定义额外函数,直接使用 functools.partial 帮助构造
# DelayFunc 实例
return functools.partial(DelayFunc, duration)


@delay(duration=2)
def add(a, b):
return a + b


# 这次调用将会延迟 2 秒
add(1, 2)
# 这次调用将会立即执行
add.eager_call(1, 2)

3.3 使用 wrapt 模块编写更扁平的装饰器#

在写装饰器时候,经常会遇到下面两类问题:

  • 装饰器需要传递参数,例如 2.2 所示。这类装饰器嵌套很多层,很容易’绕’晕了。

  • 为函数编写的装饰器,无法直接用于类方法上

示例#

一个随机生成[min,max]之间的数字

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


def randomint(min, max):
def inner(func):
def wapper(*args, **kwargs):
num = random.randint(min, max)
return func(num, *args, **kwargs)

return wapper

return inner


@randomint(1, 45)
def p(num):
print(num)


p()
# 输出 23

这个示例多次嵌套,绕来绕去。。。。

而且不能用于类方法上

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


def randomint(min, max):
def inner(func):
def wapper(*args, **kwargs):
num = random.randint(min, max)
return func(num, *args, **kwargs)

return wapper

return inner


class Example():
@randomint(1, 45)
def p(self, num):
print(num)


e = Example()
e.p()
# 输出 <__main__.Example object at 0x101e62908>

所以,需要有一个更好的方法解决这些问题。那就需要 wrapt

上例可以改为

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


def randomint(min, max):
@wrapt.decorator
def inner(wrapped, instance, args, kwargs):
num = random.randint(min, max)
return wrapped(num, *args, **kwargs)

return inner


class Example():
@randomint(1, 45)
def p(self, num):
print(num)


e = Example()
e.p()
# 输出 34

wrapt使用说明#

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 wrapt
import time


# 统计执行时间的装饰器
@wrapt.decorator
def statime(wrapped, instance, args, kwargs):
s = time.time()
res = wrapped(*args, **kwargs)
print(time.time() - s)
return res


@statime
def add(x, y):
time.sleep(1)
return x + y


if __name__ == '__main__':
res = add(1, 2)
print(res)

# 输出 3

说明:

  1. 使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs)

  2. 第一个参数wrapped 表示被装饰的函数或类方法,说明:

    1
    2
    3
     如果被装饰者为普通类方法,该值为类实例
    如果被装饰者为 classmethod 类方法,该值为类
    如果被装饰者为类/函数/静态方法,该值为 None
  3. 第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。

  4. args和kwargs也是固定的,注意前面没有星号

偏函数partial#

在应用中,部分参数已经知晓,其他参数未知的情况下,传入已经知晓的参数创建一个实例。补齐其他参数后,即可运行这个新实例。

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


class Dog():
def __init__(self, age, name, *args, **kwargs):
self.age = age
self.name = name


# 传入name,并创建新实例
jingba = functools.partial(Dog, name="jingba")
print(jingba)
# functools.partial(<class '__main__.Dog'>, name='jingba')

# 调用新实例,补齐参数
d1 = jingba(age=1)
print(d1)
# <__main__.Dog object at 0x104062860>

print(d1.name)
# jingba
print(d1.age)
# 1