关于前端:是碰巧还是执着python所阅读的每一场知识点唯一的共同点就是参赛选手中有详解Python的装饰器

10次阅读

共计 10396 个字符,预计需要花费 26 分钟才能阅读完成。

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 inspect
caller_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_wrapper
return 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 = func

def __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):

@staticmethod
def 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 wrapper
print '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  # 装璜实例办法,OK
def run(self):
    print "{} is running!".format(self.model)

@logging  # 装璜静态方法,Failed
@staticmethod
def 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 之前装璜,OK
def 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.decorator
def 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 技巧, 以及人工智能、大数据、数据挖掘、自动化办公等的学习办法。
打造从零根底到我的项目开发上手实战全方位解析!

正文完
 0