万字深度解析Python装饰器,你学废了吗?,滴滴面试算法题

在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。

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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
img

nvert/46506ae54be168b93cf63939786134ca.png)


既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
[外链图片转存中…(img-RD7Q4DZJ-1712774832140)]

  • 20
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值