对于Python学习者,一旦过了入门阶段,你简直肯定会用到Python的装璜器。

它常常应用在很多中央,比方Web开发,日志解决,性能收集,权限管制等。

还有一个极其重要的中央,那就是面试的时候。对,装璜器是面试中最常见的问题之一!

实战入门

抛出问题

看这段代码:

def step1(): print('step1.......')def step2(): print('step2......')def step3(): print('step3......')step1()step2()step3()

代码中定义了3个函数,而后别离调用这3个函数。假如,咱们发现代码运行很慢,咱们想晓得每个函数运行别离花了多少工夫。

笨办法解决

咱们能够在每个函数中增加计时的代码:

  • 第一行记录开始工夫
  • 执行完业务逻辑记录完结工夫
  • 完结工夫减去开始工夫,算出函数执行用时

上面的例子只在step1中增加了相干代码作为示例,你能够自行给step2和step3增加相干代码。

import timedef 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 timedef timer(func): '''统计函数运行工夫的装璜器''' def wrapper():  start = time.time()  func()  end = time.time()  used = end - start  print(f'{func.__name__} used {used}') return wrapperdef 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 timedef timer(func): '''统计函数运行工夫的装璜器''' def wrapper():  start = time.time()  func()  end = time.time()  used = end - start  print(f'{func.__name__} used {used}') return wrapper@timerdef step1(): print('step1.......')@timerdef step2(): print('step2......')@timerdef step3(): print('step3......')step1()step2()step3()

到了这里,装璜器的外围概念就讲完了。

进阶用法

下面是一个最简略的例子,被装璜的函数既没有参数,也没有返回值。上面来看有参数和返回值的状况。

带参数的函数

咱们把step1批改一下,传入一个参数,示意要走几步。

import timedef timer(func): '''统计函数运行工夫的装璜器''' def wrapper():  start = time.time()  func()  end = time.time()  used = end - start  print(f'{func.__name__} used {used}') return wrapper@timerdef step1(num): print(f'我走了#{num}步')step1(5)

再去运行,就报错了:

TypeError: wrapper() takes 0 positional arguments but 1 was given

这是因为,外表上咱们写的是step1(5),实际上Python是先调用wrapper()函数。这个函数不承受参数,所以报错了。

为了解决这个问题,咱们只有给wrapper加上参数就能够。

import timedef 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 timedef 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@timerdef add(num1, num2): return num1 + num2sum = 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@debugdef add(a, b):    return a + badd(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@slowdef add(a, b):    return a + badd(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 @slowclass 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 secondvisiting: 1visiting: 2Counter sleeping 1 secondvisiting: 1visiting: 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@singletonclass 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: 1visiting: 2visiting: 3visiting: 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@countdef run():    print('跑步有利于身体健康,来一圈')run()run()run()

运行后果:

run:第1次调用跑步有利于身体健康,来一圈run:第2次调用跑步有利于身体健康,来一圈run:第3次调用跑步有利于身体健康,来一圈

关键点就在于这一行:

wrapper_count.count = 0

给wrapper_count函数增加了count属性,来记录函数调用的次数,它也是一个有状态的装璜器。

多个装璜器嵌套

一个函数只能有一个装璜器吗?

装璜器的实质就是先调用装璜器,装璜器再调用函数。既然这样,那么多调用几层也不妨吧。

来看这个例子:

import timefrom slow import slowdef 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@timerdef 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” 即可收费取得