Python中的装璜器是你进入Python大门的一道坎,不论你跨不跨过来它都在那里。
为什么须要装璜器
咱们假如你的程序实现了say_hello()和say_goodbye()两个函数。
def say_hello():
print "hello!"
def say_goodbye():
print "hello!" # bug here
if name == '__main__':
say_hello()say_goodbye()
然而在理论调用中,咱们发现程序出错了,下面的代码打印了两个hello。通过调试你发现是say_goodbye()出错了。老板要求调用每个办法前都要记录进入函数的名称,比方这样:
Hello!
Goodbye!
好,小A是个毕业生,他是这样实现的。
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能够这样写。
def debug():
import inspectcaller_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福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
概括的讲,装璜器的作用就是为曾经存在的函数或对象增加额定的性能。
怎么写一个装璜器
在早些时候 (Python Version < 2.4,2004年以前),为一个函数增加额定性能的写法是这样的。
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中反对了@语法糖,上面代码等同于晚期的写法。所以想学的同学,有必要听一下这位老师的课、支付python福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
def debug(func):
def wrapper(): print "[DEBUG]: enter {}()".format(func.__name__) return func()return wrapper
@debug
def say_hello():
print "hello!"
这是最简略的装璜器,然而有一个问题,如果被装璜的函数须要传入参数,那么这个装璜器就坏了。因为返回的函数并不能承受参数,你能够指定装璜器函数wrapper承受和原函数一样的参数,比方:
def debug(func):
def wrapper(something): # 指定一毛一样的参数 print "[DEBUG]: enter {}()".format(func.__name__) return func(something)return wrapper # 返回包装过函数
@debug
def say(something):
print "hello {}!".format(something)
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你本人的函数,他人的函数参数是什么样子,鬼晓得?还好Python提供了可变参数args和关键字参数*kwargs,有了这两个参数,装璜器就能够用于任意指标函数了。
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)
至此,你已齐全把握高级的装璜器写法。
高级一点的装璜器
带参数的装璜器和类装璜器属于进阶的内容。在了解这些装璜器之前,最好对函数的闭包和装璜器的接口约定有肯定理解。(参见Python的闭包)
带参数的装璜器
假如咱们前文的装璜器须要实现的性能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装璜器就会是这样的。
def logging(level):
def wrapper(func): def inner_wrapper(*args, **kwargs): print "[{level}]: enter function {func}()".format( level=level, func=func.__name__) return func(*args, **kwargs) return inner_wrapperreturn wrapper
@logging(level='INFO')
def say(something):
print "say {}!".format(something)
如果没有应用@语法,等同于
say = logging(level='INFO')(say)
@logging(level='DEBUG')
def do(something):
print "do {}...".format(something)
if name == '__main__':
say('hello')do("my work")
是不是有一些晕?你能够这么了解,当带参数的装璜器被打在某个函数上时,比方@logging(level='DEBUG'),它其实是一个函数,会马上被执行,只有这个它返回的后果是一个装璜器时,那就没问题。细细再领会一下。
基于类实现的装璜器
装璜器函数其实是这样一个接口束缚,它必须承受一个callable对象作为参数,而后返回一个callable对象。在Python中个别callable对象都是函数,但也有例外。只有某个对象重载了__call__()办法,那么这个对象就是callable的。
class Test():
def __call__(self): print 'call me!'
t = Test()
t() # call me
像__call__这样前后都带下划线的办法在Python中被称为内置办法,有时候也被称为魔法办法。重载这些魔法办法个别会扭转对象的外部行为。下面这个例子就让一个类对象领有了被调用的行为。
回到装璜器上的概念上来,装璜器要求承受一个callable对象,并返回一个callable对象(不太谨严,详见后文)。那么用类来实现也是也能够的。咱们能够让类的构造函数__init__()承受一个函数,而后重载__call__()并返回一个函数,也能够达到装璜器函数的成果。所以想学的同学,有必要听一下这位老师的课、支付python福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
class logging(object):
def __init__(self, func): self.func = funcdef __call__(self, *args, **kwargs): print "[DEBUG]: enter function {func}()".format( func=self.func.__name__) return self.func(*args, **kwargs)
@logging
def say(something):
print "say {}!".format(something)
带参数的类装璜器
如果须要通过类模式实现带参数的装璜器,那么会比后面的例子略微简单一点。那么在构造函数里承受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。而后在重载__call__办法是就须要承受一个函数并返回一个函数。
class logging(object):
def __init__(self, level='INFO'): self.level = level def __call__(self, func): # 承受函数 def wrapper(*args, **kwargs): print "[{level}]: enter function {func}()".format( level=self.level, func=func.__name__) func(*args, **kwargs) return wrapper #返回函数
@logging(level='INFO')
def say(something):
print "say {}!".format(something)
内置的装璜器
内置的装璜器和一般的装璜器原理是一样的,只不过返回的不是函数,而是类对象,所以更难了解一些。
@property
在理解这个装璜器前,你须要晓得在不应用装璜器怎么写一个属性。
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
create a property
x = property(getx, setx, delx, "I am doc for x property")
以上就是一个Python属性的规范写法,其实和Java挺像的,然而太罗嗦。有了@语法糖,能达到一样的成果但看起来更简略。
@property
def x(self): ...
等同于
def x(self): ...
x = property(x)
属性有三个装璜器:setter, getter, deleter ,都是在property()的根底上做了一些封装,因为setter和deleter是property()的第二和第三个参数,不能间接套用@语法。getter装璜器和不带getter的属性装璜器成果是一样的,预计只是为了凑数,自身没有任何存在的意义。通过@property装璜过的函数返回的不再是一个函数,而是一个property对象。
property()
<property object at 0x10ff07940>
@staticmethod,@classmethod
有了@property装璜器的理解,这两个装璜器的原理是差不多的。@staticmethod返回的是一个staticmethod类对象,而@classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。
class classmethod(object):
"""classmethod(function) -> method""" def __init__(self, function): # for @classmethod decorator pass# ...
class staticmethod(object):
"""staticmethod(function) -> method"""def __init__(self, function): # for @staticmethod decorator pass# ...
装璜器的@语法就等同调用了这两个类的构造函数。
class Foo(object):
@staticmethoddef bar(): pass# 等同于 bar = staticmethod(bar)
至此,咱们上文提到的装璜器接口定义能够更加明确一些,装璜器必须承受一个callable对象,其实它并不关怀你返回什么,能够是另外一个callable对象(大部分状况),也能够是其余类对象,比方property。所以想学的同学,有必要听一下这位老师的课、支付python福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
装璜器里的那些坑
装璜器能够让你代码更加优雅,缩小反复,但也不全是长处,也会带来一些问题。
地位谬误的代码
让咱们间接看示例代码。
def html_tags(tag_name):
print 'begin outer function.'def wrapper_(func): print "begin of inner wrapper function." def wrapper(*args, **kwargs): content = func(*args, **kwargs) print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content) print 'end of inner wrapper function.' return wrapperprint 'end of outer function'return wrapper_
@html_tags('b')
def hello(name='Toby'):
return 'Hello {}!'.format(name)
hello()
hello()
在装璜器中我在各个可能的地位都加上了print语句,用于记录被调用的状况。你晓得他们最初打印进去的程序吗?如果你心里没底,那么最好不要在装璜器函数之外增加逻辑性能,否则这个装璜器就不受你管制了。以下是输入后果:
begin outer function.
end of outer function
begin of inner wrapper function.
end of inner wrapper function.
Hello Toby!
Hello Toby!
谬误的函数签名和文档
装璜器装璜过的函数看上去名字没变,其实曾经变了。
def logging(func):
def wrapper(*args, **kwargs): """print log before a function.""" print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) return func(*args, **kwargs)return wrapper
@logging
def say(something):
"""say something"""print "say {}!".format(something)
print say.__name__ # wrapper
为什么会这样呢?只有你想想装璜器的语法糖@代替的货色就明确了。@等同于这样的写法。
say = logging(say)
logging其实返回的函数名字刚好是wrapper,那么下面的这个语句刚好就是把这个后果赋值给say,say的__name__天然也就是wrapper了,不仅仅是name,其余属性也都是来自wrapper,比方doc,source等等。
应用规范库里的functools.wraps,能够根本解决这个问题。
from functools import wraps
def logging(func):
@wraps(func)def wrapper(*args, **kwargs): """print log before a function.""" print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) return func(*args, **kwargs)return wrapper
@logging
def say(something):
"""say something"""print "say {}!".format(something)
print say.__name__ # say
print say.__doc__ # say something
看上去不错!次要问题解决了,但其实还不太完满。因为函数的签名和源码还是拿不到的。
import inspect
print inspect.getargspec(say) # failed
print inspect.getsource(say) # failed
如果要彻底解决这个问题能够借用第三方包,比方wrapt。后文有介绍。
不能装璜@staticmethod 或者 @classmethod
当你想把装璜器用在一个静态方法或者类办法时,不好意思,报错了。
class Car(object):
def __init__(self, model): self.model = model@logging # 装璜实例办法,OKdef run(self): print "{} is running!".format(self.model)@logging # 装璜静态方法,Failed@staticmethoddef check_model_for(obj): if isinstance(obj, Car): print "The model of your car is {}".format(obj.model) else: print "{} is not a car!".format(obj)
"""
Traceback (most recent call last):
...
File "example_4.py", line 10, in logging
@wraps(func)
File "C:\Python27\lib\functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'staticmethod' object has no attribute '__module__'
"""
后面曾经解释了@staticmethod这个装璜器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不合乎装璜器要求的(比方传入一个callable对象),你天然不能在它之上再加别的装璜器。要解决这个问题很简略,只有把你的装璜器放在@staticmethod之前就好了,因为你的装璜器返回的还是一个失常的函数,而后再加上一个@staticmethod是不会出问题的。
class Car(object):
def __init__(self, model): self.model = model@staticmethod@logging # 在@staticmethod之前装璜,OKdef check_model_for(obj): pass
如何优化你的装璜器
嵌套的装璜函数不太直观,咱们能够应用第三方包类改良这样的状况,让装璜器函数可读性更好。
decorator.py
decorator.py 是一个非常简单的装璜器增强包。你能够很直观的先定义包装函数wrapper(),再应用decorate(func, wrapper)办法就能够实现一个装璜器。
from decorator import decorate
def wrapper(func, args, *kwargs):
"""print log before a function."""print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)return func(*args, **kwargs)
def logging(func):
return decorate(func, wrapper) # 用wrapper装璜func
你也能够应用它自带的@decorator装璜器来实现你的装璜器。
from decorator import decorator
@decorator
def logging(func, args, *kwargs):
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)return func(*args, **kwargs)
decorator.py实现的装璜器能残缺保留原函数的name,doc和args,惟一有问题的就是inspect.getsource(func)返回的还是装璜器的源代码,你须要改成inspect.getsource(func.__wrapped__)。
wrapt
wrapt是一个性能十分欠缺的包,用于实现各种你想到或者你没想到的装璜器。应用wrapt实现的装璜器你不须要放心之前inspect中遇到的所有问题,因为它都帮你解决了,甚至inspect.getsource(func)也准确无误。
import wrapt
without argument in decorator
@wrapt.decorator
def logging(wrapped, instance, args, kwargs): # instance is must
print "[DEBUG]: enter {}()".format(wrapped.__name__)return wrapped(*args, **kwargs)
@logging
def say(something): pass
应用wrapt你只须要定义一个装璜器函数,然而函数签名是固定的,必须是(wrapped, instance, args, kwargs),留神第二个参数instance是必须的,就算你不必它。当装璜器装璜在不同地位时它将失去不同的值,比方装璜在类实例办法时你能够拿到这个类实例。依据instance的值你可能更加灵便的调整你的装璜器。另外,args和kwargs也是固定的,留神后面没有星号。在装璜器外部调用原函数时才带星号。所以想学的同学,有必要听一下这位老师的课、支付python福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
如果你须要应用wrapt写一个带参数的装璜器,能够这样写。
def logging(level):
@wrapt.decoratordef wrapper(wrapped, instance, args, kwargs): print "[{}]: enter {}()".format(level, wrapped.__name__) return wrapped(*args, **kwargs)return wrapper
@logging(level="INFO")
def do(work): pass
对于wrapt的应用,倡议查阅官网文档,在此不在赘述。
小结
Python的装璜器和Java的注解(Annotation)并不是同一回事,和C#中的个性(Attribute)也不一样,齐全是两个概念。
装璜器的理念是对原函数、对象的增强,相当于从新封装,所以个别装璜器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会施展其作用。比方@logging装璜器能够在函数执行时额定输入日志,@cache装璜过的函数能够缓存计算结果等等。
而注解和个性则是对指标函数或对象增加一些属性,相当于将其分类。这些属性能够通过反射拿到,在程序运行时对不同的个性函数或对象加以干涉。比方带有Setup的函数就当成筹备步骤执行,或者找到所有带有TestMethod的函数顺次执行等等。所以想学的同学,有必要听一下这位老师的课、支付python福利奥,想学的同学能够到梦子老师的围鑫(同音):前排的是:762,两头一排是:459,后排的一组是:510 ,把以上三组字母依照程序组合起来即可她会安顿学习的。
至此我所理解的装璜器曾经讲完,然而还有一些内容没有提到,比方装璜类的装璜器。有机会再补充。谢谢观看。你要不要也来试试,用 Python 测测你和女神的颜值差距(仅供娱乐,请勿联想) 如果真的遇到好的共事,那算你背运,加油,放松学到手。
python、爬虫技巧资源分享增加围鑫(同音):762459510
蕴含python, pythonweb、爬虫、数据分析等Python技巧,以及人工智能、大数据、数据挖掘、自动化办公等的学习办法。
打造从零根底到我的项目开发上手实战全方位解析!