return wrapper
@logging(level=‘INFO’)
def say(something):
print “say {}!”.format(something)
@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__ ()
并返回一个函数,也可以达到装饰器函数的效果。
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 >
@ 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
最后
🍅 硬核资料:关注即可领取PPT模板、简历模板、行业经典书籍PDF。
🍅 技术互助:技术群大佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python爬虫全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:python)
佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。
🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python爬虫全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:python)
[外链图片转存中…(img-ArLz6uce-1711077109978)]