Python 内置装饰器
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。
@property
在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。
def getWidth(self):
return self.__width
def setWidth(self, newwidth):
self.__width = newwidth
width = property(getWidth, setWidth)
以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。
@property
def width(self):
return self.__width
@width.setter
def width(self, newWidth):
self.__width = newWidth
属性有三个装饰器: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
装饰器里的缺点
装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。
位置错误的代码
让我们直接看示例代码。
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.
<b>Hello Toby!</b>
<b>Hello Toby!</b>
错误的函数签名和文档
装饰器装饰过的函数看上去名字没变,其实已经变了。
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也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。
如果你需要使用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
Python 内置装饰器的更多相关文章
- python内置装饰器
前言 接着上一篇笔记,我们来看看内置装饰器property.staticmethod.classmethod 一.property装饰器 1. 普通方式修改属性值 code class Celsius ...
- Python内置装饰器@property
在<Python装饰器(Decorators )>一文中介绍了python装饰器的概念,日常写代码时有一个装饰器很常见,他就是内置的@property. 我们一步步的来接近这个概念. 一个 ...
- 面向对象之classmethod和staticmethod(python内置装饰器)
对象的绑定方法复习classmethodstaticmethod TOC 对象的绑定方法复习 由对象来调用 会将对象当做第一个参数传入 若对象的绑定方法中还有其他参数,会一并传入 classmetho ...
- python进阶04 装饰器、描述器、常用内置装饰器
python进阶04 装饰器.描述器.常用内置装饰器 一.装饰器 作用:能够给现有的函数增加功能 如何给一个现有的函数增加执行计数的功能 首先用类来添加新功能 def fun(): #首先我们定义一个 ...
- python基础语法16 面向对象3 组合,封装,访问限制机制,内置装饰器property
组合: 夺命三问: 1.什么是组合? 组合指的是一个对象中,包含另一个或多个对象. 2.为什么要用组合? 减少代码的冗余. 3.如何使用组合? 耦合度: 耦: 莲藕 ---> 藕断丝连 - 耦合 ...
- classmethod、staticclassmethod内置装饰器函数
# method 英文是方法的意思 # classmethod 类方法 # 当一个类中的方法中只涉及操作类的静态属性时,此时在逻辑上,我们想要直接通过类名就可以调用这个方法去修改类的静态属性,此时可以 ...
- property内置装饰器函数和@name.setter、@name.deleter
# property # 内置装饰器函数 只在面向对象中使用 # 装饰后效果:将类的方法伪装成属性 # 被property装饰后的方法,不能带除了self外的任何参数 from math import ...
- python基础--定义装饰器(内置装饰器)
装饰器的定义: 装饰器本质上就是一个python函数,它可以让其它函数在不需要做任何代码改动的前提下增加额外的功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景中,比如-- >插入 ...
- python基础-内置装饰器classmethod和staticmethod
面向对象编程之classmethod和staticmethod classmethod 和 staticmethod都是python内置的装饰器 classmethod 的作用:给在类内部定义的方法装 ...
随机推荐
- Linux使用技巧(一):vim中选中多行、复制和粘贴
一.选中多行: 使用命令vim打开文件,移动光标至所选行的开头,按v进入视图,键盘上按向下键,选中所需行即可. 二.复制与粘贴 三种方法: 1)在上文第一部分的基础上,按y,然后移动光标至所需粘贴处, ...
- 【BZOJ3105】【CQOI2013】新Nim游戏
Description 传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同).两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴.可以只拿一根,也可以拿走整堆火柴 ...
- docker attach 和 docker exec
docker attach docker attach -- Attach to a running container. 常用选项: --sig-proxy=true:Proxy all recei ...
- MVC中使用RadioButtonFor
http://shw3588.blog.163.com/blog/static/6507576201321395845538/ 1 进行初始化 <%=Html.RadioButtonFor(mo ...
- linux内核增加系统调用--Beginner's guide
Linux内核中设置了一组用于实现系统功能的子程序,称为系统调用.系统调用和普通库函数调用非常相似明知是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态. ...
- git<Commit和Push的区别>
git作为支持分布式版本管理的工具,它管理的库(repository)分为本地库.远程库. git commit操作的是本地库,git push操作的是远程库. git commit是将本地修改过的文 ...
- ACF/PACF,残差白噪声的检验问题
关于自相关.偏自相关: 一.自协方差和自相关系数 p阶自回归AR(p) 自协方差 r(t,s)=E[X(t)-EX(t)][X(s)-EX(s)] 自相关系数ACF ...
- C陷阱与缺陷的个人知识点摘录
编译过程的一点心得体会: .h文件其实只在预处理的过程用到,用来将类似#include <stdio.h>这样的行展开为具体内容. 那些标准库或者其他库中的函数,是在链接的过程中连接器把相 ...
- python pop()
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值. obj -- 可选参数,要移除列表元素的索引值,不能超过列表总长度,默认为 index=-1,删除最后一个列表值 l ...
- JAVA 线程池基本总结
合理利用线程池能够带来三个好处. 第一:降低资源消耗.通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 第二:提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 第三:提高线程 ...