在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。
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 >
@ 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 。
装饰器里的那些坑
装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。
位置错误的代码
让我们直接看示例代码。
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!< /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 wrapsdef 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
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 写一个带参数的装饰器,可以这样写。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
nvert/46506ae54be168b93cf63939786134ca.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
[外链图片转存中…(img-RD7Q4DZJ-1712774832140)]