共计 9512 个字符,预计需要花费 24 分钟才能阅读完成。
对于 Python 学习者,一旦过了入门阶段,你简直肯定会用到 Python 的装璜器。
它常常应用在很多中央,比方 Web 开发,日志解决,性能收集,权限管制等。
还有一个极其重要的中央,那就是面试的时候。对,装璜器是面试中最常见的问题之一!
实战入门
抛出问题
看这段代码:
def step1():
print('step1.......')
def step2():
print('step2......')
def step3():
print('step3......')
step1()
step2()
step3()
代码中定义了 3 个函数,而后别离调用这 3 个函数。假如,咱们发现代码运行很慢,咱们想晓得每个函数运行别离花了多少工夫。
笨办法解决
咱们能够在每个函数中增加计时的代码:
- 第一行记录开始工夫
- 执行完业务逻辑记录完结工夫
- 完结工夫减去开始工夫,算出函数执行用时
上面的例子只在 step1 中增加了相干代码作为示例,你能够自行给 step2 和 step3 增加相干代码。
import time
def step1():
start = time.time()
print('step1.......')
end = time.time()
used = end - start
print(used)
def step2():
print('step2......')
def step3():
print('step3......')
step1()
step2()
step3()
这个办法可行!但用你的脚指头想想也会感觉,这个办法很繁琐,很蠢笨,很危险!
这里只有 3 个函数,如果有 30 个函数,那不是要死人啦。万一批改的时候不小心,把原来的函数给改坏了,体面都丢光了,就要被人 BS 了!
肯定有一个更好的解决办法!
用装璜器解决
更好的解决办法是应用装璜器。
装璜器并没有什么浅近的语法,它就是一个实现了给现有函数增加装璜性能的函数,仅此而已!
import time
def timer(func):
'''统计函数运行工夫的装璜器'''
def wrapper():
start = time.time()
func()
end = time.time()
used = end - start
print(f'{func.__name__} used {used}')
return wrapper
def step1():
print('step1.......')
def step2():
print('step2......')
def step3():
print('step3......')
timed_step1 = timer(step1)
timed_step2 = timer(step2)
timed_step3 = timer(step3)
timed_step1()
timed_step2()
timed_step3()
下面的 timer 函数就是个装璜器。
- 它的参数是须要被装璜的函数
- 返回值是新定义的一个包装了原有函数的函数。
- 新定义的函数先记录开始工夫,调用被装璜的函数,而后再计算用了多少工夫。
简略说就是把原来的函数给包了起来,在不扭转原函数代码的状况下,在里面起到了装璜作用,这就是传说中的装璜器。它其实就是个一般的函数。
如果你感觉有点懵逼,须要增强一些对 Python 函数的了解。函数:
能够作为参数传递
能够作为返回值
也能够定义在函数外部
而后,咱们不再间接调用 step1, 而是:
- 先调用 timer 函数,生成一个包装了 step1 的新的函数 timed_step1.
- 剩下的就是调用这个新的函数 time_step1(),它会帮咱们记录时间。
timed_step1 = timer(step1)
timed_step1()
简洁点,也能够这样写:
timer(step1)()
timer(step2)()
timer(step3)()
这样能够在不批改原有函数代码的状况下,给函数增加了装饰性的新性能。
然而依然须要批改调用函数的中央,看起来还不够简洁。有没有更好的方法呢?当然是有的!
装璜器语法糖衣
咱们能够在被装璜的函数前应用 @符号指定装璜器。这样就不必批改调用的中央了,这个世界喧扰了。上面的代码和上一段代码性能一样。在运行程序的时候,Python 解释器会依据 @标注主动生成装璜器函数,并调用装璜器函数。
import time
def timer(func):
'''统计函数运行工夫的装璜器'''
def wrapper():
start = time.time()
func()
end = time.time()
used = end - start
print(f'{func.__name__} used {used}')
return wrapper
@timer
def step1():
print('step1.......')
@timer
def step2():
print('step2......')
@timer
def step3():
print('step3......')
step1()
step2()
step3()
到了这里,装璜器的外围概念就讲完了。
进阶用法
下面是一个最简略的例子,被装璜的函数既没有参数,也没有返回值。上面来看有参数和返回值的状况。
带参数的函数
咱们把 step1 批改一下,传入一个参数,示意要走几步。
import time
def timer(func):
'''统计函数运行工夫的装璜器'''
def wrapper():
start = time.time()
func()
end = time.time()
used = end - start
print(f'{func.__name__} used {used}')
return wrapper
@timer
def step1(num):
print(f'我走了 #{num}步')
step1(5)
再去运行,就报错了:
TypeError: wrapper() takes 0 positional arguments but 1 was given
这是因为,外表上咱们写的是 step1(5),实际上 Python 是先调用 wrapper()函数。这个函数不承受参数,所以报错了。
为了解决这个问题,咱们只有给 wrapper 加上参数就能够。
import time
def timer(func):
'''统计函数运行工夫的装璜器'''
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
used = end - start
print(f'{func.__name__} used {used}')
return wrapper
- wrapper 应用了通配符,args 代表所有的地位参数,*kwargs 代表所有的关键词参数。这样就能够应答任何参数状况。
- wrapper 调用被装璜的函数的时候,只有一成不变的把参数再传递进去就能够了。
函数返回值
如果被装璜的函数 func 有返回值,wrapper 也只需把 func 的返回值返回就能够了。
import time
def timer(func):
'''统计函数运行工夫的装璜器'''
def wrapper(*args, **kwargs):
start = time.time()
ret_value = func(*args, **kwargs)
end = time.time()
used = end - start
print(f'{func.__name__} used {used}')
return ret_value
return wrapper
@timer
def add(num1, num2):
return num1 + num2
sum = add(5, 8)
print(sum)
这里我新加了一个 add 函数,计算两个数之和。
在 wrapper 函数中,咱们先保留了 func 的返回值到 ret_value,而后在 wrapper 的最初返回这个值就能够了。
到这里,你又进了一步,你能够击败 88.64% 的 Python 学习者了。但还不够,前面还有:
- 类装璜器(下面都是函数装璜器)
- 多装璜器串联
- 带参数的装璜器(不同于下面的带参数的函数)
- 带状态的装璜器
- 用类封装装璜器
- 装璜器罕用状况举例
有位同学看完后面的内容,感觉本人把握的很好了,就去面试。后果被面试官一个“如何在 Python 中实现单例模式”的问题给当场问倒了。
气得他下来就是两个耳刮子,不过不是打面试官,是打本人,恨本人没有等读透整篇再去面试。所以大家都急躁读完。
你肯定用过装璜器 Decorator
其实 Decorator 就在咱们身边,只是咱们可能不晓得它们是装璜器。我来说几个:@classmethod @staticmethod @property
对,这些很重要的语法,不过是装璜器的利用而已。
来看一个代码例子:
class Circle:
#半径用下划线结尾,示意公有变量
def __init__(self, radius):
self._radius = radius
#用 property 装璜器创立虚构的半径属性
@property
def radius(self):
return self._radius
#用 setter 装璜器给半径属性增加赋值操作
@radius.setter
def radius(self, value):
if value >= 0:
self._radius = value
else:
raise ValueError("Radius must be positive")
#用 property 装璜器创立虚构的面积属性
@property
def area(self):
return self.pi() * self.radius**2
def cylinder_volume(self, height):
return self.area * height
#类办法
@classmethod
def unit_circle(cls):
return cls(1)
#静态方法
@staticmethod
def pi():
return 3.1415926535
再来创立两个装璜器练练手
你不要认为你曾经把握了装璜器,你只是听懂了。
从听懂到能入手写进去,再到被面试的时候,能够晦涩的说进去,那还差着二十万八千里呢!
肯定得多入手!所以抓紧时间,马上再来创立两个装璜器。
代码调试装璜器
当初咱们来创立一个装璜器:它会打印函数的参数,以及返回值。
如果你有理论我的项目教训,你肯定会晓得这个很有用。这不就是主动打印日志嘛!是程序员找臭虫的必备良药啊。
来看看代码:
def debug(func):
def wrapper_debug(*args, **kwargs):
print(f'{func.__name__}:{args}, {kwargs}')
ret_val = func(*args, **kwargs)
print(f'return: {ret_val}')
return ret_val
return wrapper_debug
@debug
def add(a, b):
return a + b
add(1, 3)
add(2, 3)
add(4, 3)
在 wrapper_debug 函数中,咱们先打印所有的参数,再调用原函数,最初先打印返回值,再返回返回值。这里并没有新的语法常识,就是为了练手。
让程序跑慢点
已经我还年老,看到一个大神的代码外面有这么一行:
sleep(random(1,5))
因为有了这行代码,程序运行的时候挺慢的。我就问大神,为什么要这样。大神苦口婆心的跟我说:
你还年老!我把这个程序交付给客户,客户会感觉有点慢,但还能忍。
忍不住了,会来找我优化性能。我一个手指头就把性能优化下来了,客户肯定对我嗤之以鼻。而且咱们公司的尾款也给咱们了。
年轻人,多学着点!这就是经历,经历!
惋惜我学了这么多年,也没学会这种经历。
不过有时候,因为各种起因,咱们的确须要让程序变慢一点。装璜器就排上了用场:
import time
def slow(func):
def wrapper_slow(*args, **kwargs):
print(f'{func.__name__} sleeping 1 second')
time.sleep(1)
ret_val = func(*args, **kwargs)
return ret_val
return wrapper_slow
@slow
def add(a, b):
return a + b
add(1, 3)
运行一下,你就会很有成就感!的确慢!
下面那个实在的段子,我劝大家和我一样,始终都学不会。日久见人心,坑人的事件不能干。
装璜器模板
通过后面几个例子,咱们能够总结出一个装璜器的模板。
依照这个模板,能够轻松写出装璜器:
def decorator(func):
def wrapper_decorator(*args, **kwargs):
#调用前操作
ret_val = func(*args, **kwargs)
#调用后操作
return ret_val
return wrapper_decorator
依照这个模板:
- 批改装璜器的名字,把 decorator 替换为具体的名字。
- 在正文“调用前操作”的中央写本人想写的代码
- 在正文“调用后操作”的中央写本人想写的代码。
带参数的装璜器
下面那两个都是一般的装璜器的利用,咱们不能持续自 High 上来了。咱们得学习新常识了。
下面那个 slow 的装璜器,如果可能传入到底要 sleep 几秒就好了,当初是固定的 1 秒,这个不香。
留神辨别,这里的参数是指装璜器的参数。和后面提到的函数本身的参数是不同的。
我想让它多慢就多慢,而后咱们再顷刻间扭转乾坤,这样客户就更为我神魂颠倒了。
要让装璜器承受参数,须要在一般装璜器的里面再套上一层:
import time
def slow(seconds):
def decorator_slow(func):
def wrapper_slow(*args, **kwargs):
print(f'{func.__name__} sleeping {seconds} second')
time.sleep(seconds)
ret_val = func(*args, **kwargs)
return ret_val
return wrapper_slow
return decorator_slow
#增加装璜器的时候能够传入要加快几秒的参数。@slow(2)def add(a, b):
return a + b
#执行此行会进展 2 秒
add(1, 3)
以前的装璜器,是函数外面有一个外部函数(2 层函数),当初这个有了 3 层函数:
- 先是 slow,承受秒数作为参数
- slow 外面创立了 decorator_slow 函数,这个就是和原来一样的装璜器函数
- wrapper_slow 外面又创立了 wrapper_slow 函数。
其实前面两层就是和之前一样的,惟一的区别是里面又加了一层。
为什么会这样呢?为什么最里面一层不须要传入 func 参数呢?
这是因为:
- 当 Python 发现 slow(2)这个装璜器自带了参数时,它就不再传入以后函数作为参数,间接调用 slow。这是 Python 解释器规定的。
- slow 返回了一个函数,这时候 Python 会再把以后函数传入进去,这时候就成为一个一般的装璜器了。
这就是说最里面一层的性能就是为了解决装璜器的参数的。
如果你一下子不能了解,先把代码敲进去,你就了解了。正所谓:熟读唐诗三百首,不会吟诗也会吟!
再来看一个装璜器带参数的例子:
def repeat(nums=3):
def decorator_repeat(func):
def wrapper_repeat(*args, **kwargs):
for _ in range(nums):
func(*args, **kwargs)
return wrapper_repeat
return decorator_repeat
@repeat(3)
def run():
print('跑步有利于身体健康,来一圈')
#这里会反复执行 3 次
run()
这个装璜和 slow 装璜器一样坑人,它会多次重复执行一个办法,并且能够动静指定要反复几次。
细细品味一下这个 3 层的函数,它是如何实现带参数的装璜器的。这两个例子都懂了,你就走在吊打面试官的路上了。
类装璜器
还记得后面给本人两个耳光的同学吗?如果他当初去面试,还是给本人两个耳光,还是不晓得如何实现单例模式。
单例模式,是指一个类只能创立一个实例,是最常见的设计模式之一。比方网站程序有一个类统计网站的拜访人数,这个类只能有一个实例。如果每次拜访都创立一个新的实例,那人数就永远是 1 了。在 Python 中能够用装璜器实现单例模式。
后面的装璜器都是用来装璜函数的,或者用来装璜类办法的,比方咱们写的 slow, debug, timer; Python 自带的 staticmethod, classmethod 等。
那如果把装璜器放到类名后面会怎么呢?来看这段代码:
from slow import slow
@slow
class Counter():
def __init__(self):
self._count = 0
def visit(self):
self._count += 1
print(f'visiting: {self._count}')
c1 = Counter()
c1.visit()
c1.visit()
c2 = Counter()
c2.visit()
c2.visit()
这个类名叫 Counter(),顾名思义就是用来做计数的。它有一个外部变量叫做_count,每次调用 Counter 的 visit()办法,计数就会加 1.
第一行,咱们引入了后面写的 slow 装璜器,是那个一般的不带参数的 slow。装璜器就是个函数,当然能够被 import 进来。
这次 @slow 放在 Counter 类名后面,而不是办法的后面,会产生什么呢?运行下面的代码,会发现这样的后果:
Counter sleeping 1 second
visiting: 1
visiting: 2
Counter sleeping 1 second
visiting: 1
visiting: 2
这阐明只有在创立 Counter 实例的时候,才会 sleep 一秒,调用 visit 函数的时候,不会 sleep。
所以,类装璜器实际上装璜的是类的初始化办法。只有初始化的时候会装璜一次。
用装璜器实现单例模式
下面的运行后果很让人悲观,如果去面试,还是会给本人两个耳刮子的。
作为一个计数器,应该计数是一直叠加的。可是下面的代码,创立了两个计数器,本人记录本人的。扯淡啊!
咱们当初就用类装璜器革新它:
def singleton(cls):
'''创立一个单例模式'''
def single_wrapper(*args, **kwargs):
if not single_wrapper.instance:
single_wrapper.instance = cls(*args, **kwargs)
return single_wrapper.instance
single_wrapper.instance = None
return single_wrapper
@singleton
class Counter():
def __init__(self):
self._count = 0
def visit(self):
self._count += 1
print(f'visiting: {self._count}')
c1 = Counter()
c1.visit()
c1.visit()
c2 = Counter()
c2.visit()
c2.visit()
先来运行一下:
visiting: 1
visiting: 2
visiting: 3
visiting: 4
后果很称心,尽管创立了两个 Counter,计数是记录在一起的。这次要得益于这个新的装璜器:
def singleton(cls):
'''创立一个单例模式'''
def single_wrapper(*args, **kwargs):
#如果没有实例,则创立实例
if not single_wrapper.instance:
single_wrapper.instance = cls(*args, **kwargs)
#返回原来的实例,或者新的实例
return single_wrapper.instance
#给新创建的函数增加一个属性保留实例
single_wrapper.instance = None
return single_wrapper
它和其余的装璜器根本一样,它的不同之处在于这一行:
single_wrapper.instance = None
在创立完函数后,又给函数增加了一个属性,用来保留实例,开始为 None,就是没有实例。
再来剖析一下代码逻辑:
- 先判断是否有实例,如果没有就创立一个。反过来,曾经有了就不必创立。
- 返回实例。
把这个装璜器加到类上的时候,就相当于加到了初始化办法。
当咱们创立 Counter 的时候,被这个装璜器截胡,它会返回一个曾经创立好的实例。如果没有实例,它会创立一个。
也就是说,不论调用 Counter()多少次,最终就只有一个实例。这就是实现了单例模式。
如果有点不懂,再看一遍,为的是在面试官背后扬眉吐气。
带状态的装璜器
下面的例子中,咱们看到装璜器本人保留了一个实例,你要的时候它就给你这一个,所以才实现了单例模式。这种就叫做带状态的装璜器。
咱们再来看一个例子。count 装璜器会记录一个函数被调用的次数:
def count(func):
def wrapper_count():
wrapper_count.count += 1
print(f'{func.__name__}:第 {wrapper_count.count} 次调用')
func()
wrapper_count.count = 0
return wrapper_count
@count
def run():
print('跑步有利于身体健康,来一圈')
run()
run()
run()
运行后果:
run:第 1 次调用
跑步有利于身体健康,来一圈
run:第 2 次调用
跑步有利于身体健康,来一圈
run:第 3 次调用
跑步有利于身体健康,来一圈
关键点就在于这一行:
wrapper_count.count = 0
给 wrapper_count 函数增加了 count 属性,来记录函数调用的次数,它也是一个有状态的装璜器。
多个装璜器嵌套
一个函数只能有一个装璜器吗?
装璜器的实质就是先调用装璜器,装璜器再调用函数。既然这样,那么多调用几层也不妨吧。
来看这个例子:
import time
from slow import slow
def timer(func):
def wrapper():
start_time = time.perf_counter()
func()
end_time = time.perf_counter()
used_time = end_time - start_time
print(f'{func.__name__} used {used_time}')
return wrapper
@slow
@timer
def run():
print('跑步有利于身体健康,来一圈')
run()
这个例子中,run 函数用了两个装璜器,slow 和 timer。它的执行过程就相当于:
slow(time(run()))
从上到下调用,先是调用 slow,而后 slow 去调用 timer,而后 timer 去调用 run,所以执行后果是:
run sleeping 1 second
跑步有利于身体健康,来一圈
wrapper_slow used 1.0026384350000002
Python 装璜器宝藏库
差不多了,了解透这些原理,你就算不给面试官两个耳刮子,至多也不必给本人了。相干问题就算不是对答如流,也能轻松应答吧。最近整顿了几百 G 的 Python 学习材料,蕴含新手入门电子书、教程、源码等等,收费分享给大家!想要的返回“Python 编程学习圈”,发送“J”即可收费取得