Python 装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,
装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,
比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。
装饰器是解决这类问题的绝佳设计,有了装饰器,
我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数
装饰器的作用就是为已经存在的函数或对象添加额外的功能
装饰器的作用就是为已经存在的函数或对象添加额外的功能。
在早些时候 (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中支持了@语法糖,下面代码等同于早期的写法。
def debug(func):
def wrapper():
print "[DEBUG]: enter {}()".format(func.name)
return func()
return wrapper
@debug
def say_hello():
print "hello!"
@语法糖的执行原理就是say_hello = debug(say_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)
当传入多个参数的时候,在内层函数出,传入可变参数
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)
带参数的装饰器
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")
四种类型本质原理:
装饰器不带参数,修饰的函数也不带参数
@debug
def say_hello():
print "hello!"
say_hello = debug(say_hello)
say_hello()
debug(say_hello)()
装饰器不带参数,修饰的函数带参数
@debug
def say(something):
print "hello {}!".format(something)
say_hello = debug(say_hello)
say_hello(something)
新赋值的say_hello函数调用的时候,传入被修饰函数的形参值
say_hello(something)
debug(say_hello)(something)
装饰器带参数,修饰的函数不带参数
@debug(something)
def say():
print "hello {}!".format()
say=debug(something)(say)
say()
debug(something)(say)()
装饰器带参数,修饰的函数也带参数
@debug(something)
def say(otherthing):
print "hello {}!".format(otherthing)
say=debug(something)(say)
say(otherthing)
debug(something)(say)(otherthing)
基于类实现的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个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__()并返回一个函数,也可以达到装饰器函数的效果。
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)
say=logging(say)
say()
带参数的类装饰器-----参数需要在类的初始化函数中进行保存,然后再在__call__方法中使用
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,
而是传入的参数。通过类把这些参数保存起来。然后在重载__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)
类的装饰器实现,主要的是__call__方式的使用
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象
property() 函数的作用是在新式类中返回属性值。
参数
fget -- 获取属性值的函数
fset -- 设置属性值的函数
fdel -- 删除属性值函数
doc -- 属性描述信息
返回值
返回新式类属性
class C(object):
def init(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
如果 c 是 C 的实例化, c.x 将触发 getter,c.x = value 将触发 setter , del c.x 触发 deleter。
如果给定 doc 参数,其将成为这个属性值的 docstring,否则 property 函数就会复制 fget 函数的 docstring(如果有的话)。
将 property 函数用作装饰器可以很方便的创建只读属性:
@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)