万字深度解析Python装饰器,你学废了吗?,想跳槽涨薪的必看

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)
img

佬指点迷津,你的问题可能不是问题,求资源在群里喊一声。

🍅 面试题库:由技术群里的小伙伴们共同投稿,热乎的大厂面试真题,持续更新中。
🍅 知识体系:含编程语言、算法、大数据生态圈组件(Mysql、Hive、Spark、Flink)、数据仓库、Python、前端等等。

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python爬虫全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:python)
[外链图片转存中…(img-ArLz6uce-1711077109978)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值