常见装饰器;内置装饰器;类装饰器、函数装饰器、带参数的函数装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能
一、函数装饰器:
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) return wrapper def bar(): print('i am bar')
bar = use_logging(bar) bar()
输出:
WARNING:root:bar is running
i am bar
二、带参数的函数装饰器:
def use_logging(level): def decorator(func): def wrapper(*args, **kwargs): if level == "warn": logging.warn("%s is running" % func.__name__) return func(*args) return wrapper return decorator @use_logging(level="warn") def foo(name='foo'): print("i am %s" % name) foo()
输出:
WARNING:root:foo is running
i am foo
三、类装饰器:
再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的 __call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def bar(): print ('bar') bar()
输出:
class decorator runing
bar
class decorator ending
四、functools.wraps
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:
装饰器
def logged(func): def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging
函数
@logged def f(x): """does some math""" return x + x * x
该函数完成等价于:
def f(x): """does some math""" return x + x * x f = logged(f)
不难发现,函数f被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。
print f.__name__ # prints 'with_logging'
print f.__doc__ # prints None
这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ + " was called" return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x print f.__name__ # prints 'f' print f.__doc__ # prints 'does some math'
五、内置装饰器
@property 的用法参见:把类方法变成属性,可以通过类直接调用
https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820062641f3bcc60a4b164f8d91df476445697b9e000
http://www.cnblogs.com/superxuezhazha/p/5793450.html
因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score 注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。 现在,就可以像使用属性一样设置score了: >>> s = Student('Bob', 59) >>> s.score = 60 >>> print s.score
60
@staticmethod :静态方法
@classmethod : 类方法
参考:http://www.cnblogs.com/taceywong/p/5813166.html
Python其实有3类方法:
- 静态方法(staticmethod)
- 类方法(classmethod)
- 实例方法(instance method)
看一下下面的示例代码:
def foo(x): print "executing foo(%s)" %(x) class A(object): def foo(self,x): print "executing foo(%s,%s)" %(self,x) @classmethod def class_foo(cls,x): print "executing class_foo(%s,%s)" %(cls,x) @staticmethod def static_foo(x): print "executing static_foo(%s)" %x a = A()
在示例代码中,先理解下函数里面的self和cls。这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x)
,这个函数就是最常用的,它的工作和任何东西(类、实例)无关。对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self,x)
,为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)
(其实是foo(a,x)
)。类方法一样,只不过它传递的是类而不是实例,A.class_foo(x)
。注意这里的self和cls可以替换别的参数,但是python的约定是这两个,尽量不要更改。
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用时候需要使用a.static_foo(x)
或A.static_foo()
来调用。
实例方法 | 类方法 | 静态方法 | |
---|---|---|---|
a = A() | a.foo(x) | a.class_foo(x) | a.static_foo(x) |
A | 不可用 | A.clas_foo(x) | A.static_foo(x) |
>>> a=A() >>> a.foo(3) executing foo(<__main__.A object at 0x108117790>,3) >>> a.class_foo(3) executing class_foo(<class '__main__.A'>,3) >>> A.class_foo(3) executing class_foo(<class '__main__.A'>,3) >>> a.static_foo(3) executing static_foo(3) >>> A.static_foo(3) executing static_foo(3) >>> A.foo(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unbound method foo() must be called with A instance as first argument (got int instance instead) >>>
参考:
1、https://www.zhihu.com/question/26930016
2、http://python.jobbole.com/85056/
3、http://pythoncentral.io/difference-between-staticmethod-and-classmethod-in-python/
4、http://30daydo.com/article/89