今日推文速看
三条:精心整理!9个 Python 实用案例分享~
四条:异步 Python 比同步 Python 快在哪里?~
五条:3种Python数据结构,13种创建方法,这个总结,超赞!
三条:精心整理!9个 Python 实用案例分享~
四条:异步 Python 比同步 Python 快在哪里?~
五条:3种Python数据结构,13种创建方法,这个总结,超赞!
↑ 关注 + 星标 ,每天学Python新技能
后台回复【大礼包】送你Python自学大礼包
↑ 关注 + 星标 ,每天学Python新技能 后台回复【大礼包】送你Python自学大礼包
这是昨天 不懂Python装饰器,你敢说会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函数中,我们先打印所有的参数,再调用原函数,最后先打印返回值,再返回返回值。这里并没有新的语法知识,就是为了练手。
装B神奇 - 让程序跑慢点
曾经我还年轻,看到一个大神的代码里面有这么一行:
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官方文档为了一份装饰器列表,在搜索引擎搜:PythonDecoratorLibrary。
给你几百G视频,也不一定有用,不如短小精悍,拿来就可以用的代码。对,我的福利就是上文和本文用的所有装饰器的示例代码。
我觉得也没必要下载,直接收藏就行了,需要的时候再来下载。
最后,希望你能点赞,点再看,转发。如果不能奢求三连,就随便来点吧。谢谢您的阅读。
推荐阅读
推荐阅读
扫码回复「大礼包」后获取大礼
重磅!Python交流群已成立
公众号运营至今,离不开小伙伴们的支持。
为了给小伙伴们提供一个互相交流的技术平台,特地开通了Python交流群。 群里有不少技术大神,不时会分享一些技术要点,更有一些资源收藏爱好者不时分享一些优质的学习资料。(免费,不卖课!) 需要进群的朋友,可长按扫描下方二维码。
另外新好友福利
1,加微信发送 电子书 送一份精心整理的Python电子书一套 2,学习路上有什么问题也欢迎过来提问 3,欢迎围观龙哥的朋友圈,不定期白嫖福利第一时间通知
扫码回复「大礼包」后获取大礼
重磅!Python交流群已成立 公众号运营至今,离不开小伙伴们的支持。 为了给小伙伴们提供一个互相交流的技术平台,特地开通了Python交流群。 群里有不少技术大神,不时会分享一些技术要点,更有一些资源收藏爱好者不时分享一些优质的学习资料。(免费,不卖课!) 需要进群的朋友,可长按扫描下方二维码。 另外新好友福利 1,加微信发送 电子书 送一份精心整理的Python电子书一套 2,学习路上有什么问题也欢迎过来提问 3,欢迎围观龙哥的朋友圈,不定期白嫖福利第一时间通知