资讯专栏INFORMATION COLUMN

《Python有什么好学的》之修饰器

lewinlee / 2412人阅读

摘要:然后煎鱼加了一个后再调用函数,得到的输出结果和加修饰器的一样,换言之等效于因此,我们对于,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。

“Python有什么好学的”这句话可不是反问句,而是问句哦。

主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简单)。

于是我便记录一下,如果要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其他语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。

什么是修饰器,为什么叫修饰器

修饰器英文是Decorator,

我们假设这样一种场景:古老的代码中有几个很是复杂的函数F1、F2、F3...,复杂到看都不想看,反正我们就是不想改这些函数,但是我们需要改造加功能,在这个函数的前后加功能,这个时候我们很容易就实现这个需求:

def hi():
    """hi func,假装是很复杂的函数"""
    return "hi"

def aop(func):
    """aop func"""
    print("before func")
    print(func())
    print("after func")
    
if __name__ == "__main__":
    aop(hi)

以上是很是简单的实现,利用Python参数可以传函数引用的特性,就可以实现了这种类似AOP的效果。

这段代码目前没有什么问题,接下来煎鱼加需求:需求为几十个函数都加上这样的前后的功能,而所有调用这些函数地方也要相应地升级。

看起来这个需求比较扯,偏偏这个需求却是较为广泛:在调用函数的前后加上log输出、在调用函数的前后计算调用时间、在调用函数的前后占用和释放资源等等。

一种比较笨的方法就是,为这几十个函数逐一添加一个入口函数,针对a函数添加一个a_aop函数,针对b函数添加一个b_aop函数...如此这样。问题也很明显:

工作量大

代码变得臃肿复杂

原代码有多处调用了这些函数,可以会升级不完全

于是接下来有请修饰器出场,修饰器可以统一地给这些函数加这样的功能:

def aop(func):
    """aop func"""
    def wrapper():
        """wrapper func"""
        print("before func")
        func()
        print("after func")
    return wrapper

@aop
def hi():
    """hi func"""
    print("hi")
    
@aop
def hello():
    """hello func"""
    print("hello")

if __name__ == "__main__":
    hi()
    hello()

以上aop函数就是修饰器的函数,使用该修饰器时只要在待加函数上一行加@修饰器函数名即可,如实例代码中就是@aop

加上了@aop后,调用新功能的hi函数就喝原来的调用一样:就是hi()而不是aop(hi),也意味着所有调用这些函数的地方不需要修改就可以升级。

简单地来说,大概修饰器就是以上的这样子。

@是个什么

对于新手来说,上面例子中,@就是一样奇怪的东西:为什么这样子用就可以实现煎鱼需求的功能了。

其实我们还可以不用@,煎鱼换一种写法:

def hi():
    """hi func"""
    print("hi")

def aop(func):
    """aop func"""
    def wrapper():
        """wrapper func"""
        print("before func")
        func()
        print("after func")
    return wrapper

if __name__ == "__main__":
    hi()

    print("")

    hi = aop(hi)
    hi()

上面的例子中的aop函数就是之前说过的修饰器函数。

如例子main函数中第一次调用hi函数时,由于hi函数没叫修饰器,因此我们可以从输出结果中看到程序只输出了一个hi而没有前后功能。

然后煎鱼加了一个hi = aop(hi)后再调用hi函数,得到的输出结果和加修饰器的一样,换言之:

@aop 等效于hi = aop(hi)

因此,我们对于@,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。

有点拗口。aop(hi)是新函数的引用,至于返回了引用的原因是aop函数中运用闭包返回了函数引用。而hi这个函数的引用,本来是指向旧函数的,通过hi = aop(hi)赋值后,就指向新函数了。

被调函数加参数

以上的例子中,我们都假设被调函数是无参的,如hi、hello函数都是无参的,我们再看一眼煎鱼刚才的写的修饰器函数:

def aop(func):
    """aop func"""
    def wrapper():
        """wrapper func"""
        print("before func")
        func()
        print("after func")
    return wrapper

很明显,闭包函数wrapper中,调用被调函数用的是func(),是无参的。同时就意味着,如果func是一个带参数的函数,再用这个修饰器就会报错。

@aop
def hi_with_deco(a):
    """hi func"""
    print("hi" + str(a))

if __name__ == "__main__":
    # hi()
    hi_with_deco(1)

就是参数的问题。这个时候,我们把修饰器函数改得通用一点即可,其中import了一个函数(也是修饰器函数):

from functools import wraps

def aop(func):
    """aop func"""
    @wraps(func)
    def wrap(*args, **kwargs):
        print("before")
        func(*args, **kwargs)
        print("after")

    return wrap

@aop
def hi(a, b, c):
    """hi func"""
    print("test hi: %s, %s, %s" % (a, b, c))

@aop
def hello(a, b):
    """hello func"""
    print("test hello: %s, %s" % (a, b))

if __name__ == "__main__":
    hi(1, 2, 3)
    hello("a", "b")

这是一种很奇妙的东西,就是在写修饰器函数的时候,还用了别的修饰器函数。那也没什么,毕竟修饰器函数也是函数啊,有什么所谓。

带参数的修饰器

思路到了这里,煎鱼不禁思考一个问题:修饰器函数也是函数,那函数也是应该能传参的。函数传参的话,不同的参数可以输出不同的结果,那么,修饰器函数传参的话,不同的参数会怎么样呢?

其实很简单,修饰器函数不同的参数,能生成不同的修饰器啊。

如,我这次用这个修饰器是把时间日志打到test.log,而下次用修饰器的时候煎鱼希望是能打到test2.log。这样的需求,除了写两个修饰器函数外,还可以给修饰器加参数选项:

from functools import wraps

def aop_with_param(aop_test_str):
    def aop(func):
        """aop func"""
        @wraps(func)
        def wrap(*args, **kwargs):
            print("before " + str(aop_test_str))
            func(*args, **kwargs)
            print("after " + str(aop_test_str))
        return wrap
    return aop

@aop_with_param("abc")
def hi(a, b, c):
    """hi func"""
    print("test hi: %s, %s, %s" % (a, b, c))

@aop_with_param("pppppp")
def hi2(a, b, c):
    """hi func"""
    print("test hi: %s, %s, %s" % (a, b, c))

if __name__ == "__main__":
    hi(1, 2, 3)
    print("")
    hi2(2, 3, 4)

同样的,可以加一个参数,也可以加多个参数,这里就不说了。

修饰器类

大道同归,逻辑复杂了之后,人们都喜欢将函数的思维层面抽象上升到对象的层面。原因往往是对象能拥有多个函数,对象往往能管理更复杂的业务逻辑。

显然,修饰器函数也有对应的修饰器类。写起来也没什么难度,和之前的生成器一样简单:

from functools import wraps

class aop(object):
    def __init__(self, aop_test_str):
        self.aop_test_str = aop_test_str

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("before " + self.aop_test_str)
            func()
            print("after " + self.aop_test_str)

        return wrapper
        
@aop("pppppp")
def hi():
    print("hi")

看得出来,这个修饰器类也不过是多了个__call__函数,而这个__call__函数的内容和之前写的修饰器函数一个样!而使用这个修饰器的方法,和之前也一样,一样的如例子中的@aop("pppppp")

甚至,煎鱼过于无聊,还试了一下继承的修饰器类:

class sub_aop(aop):
    def __init__(self, sub_aop_str, *args, **kwargs):
        self.sub_aop_str = sub_aop_str
        super(sub_aop, self).__init__(*args, **kwargs)

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print("before " + self.sub_aop_str)
            super(sub_aop, self).__call__(func)()
            print("after " + self.sub_aop_str)
        return wrapper
        
@sub_aop("ssssss", "pppppp")
def hello():
    print("hello")
    
if __name__ == "__main__":
    hello()

你们猜猜结果怎么样?

先这样吧

若有错误之处请指出,更多地请关注造壳。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44953.html

相关文章

  • Python什么好学上下文管理

    摘要:引上下文管理器太极生两仪,两仪为阴阳。而最常用的则是,即上下文管理器使用上下文管理器用之后的文件读写会变成我们看到用了之后,代码没有了创建,也没有了释放。实现上下文管理器我们先感性地对进行猜测。现实一个上下文管理器就是这么简单。 Python有什么好学的这句话可不是反问句,而是问句哦。 主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可...

    qpwoeiru96 评论0 收藏0
  • Python什么好学生成/迭代

    摘要:为什么是斐波那契谈到生成器迭代器,人们总是喜欢用斐波那契数列来举例。那么,换句话来说,即能由推导式得出的数列,其实都可以用来做生成器迭代器的例子。然而,生成器和生成器类的实例都属于迭代器。 Python有什么好学的这句话可不是反问句,而是问句哦。 主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写...

    n7then 评论0 收藏0
  • Python函数修饰---当方法前遇到@参数化修饰方法时发生

    一、前提概念   Python中的函数是对象。也因此,函数可以被当做变量使用。 二、代码模型 以下代码片段来自于: http://www.sharejs.com/codes/python/8361 # -*- coding: utf-8 -*- from threading import Thread import time class TimeoutEx...

    huashiou 评论0 收藏0
  • Python装饰

    摘要:一引用书流畅的书二基本概念问题装饰器是什么解答严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数问题装饰器有什么特性解答装饰器有个特性,一是可以把被装饰的函数替换成其他函数, 一, 引用 [书] 流畅的Python [书] Effective Python 二, 基本概念 showImg(https://segme...

    aisuhua 评论0 收藏0
  • 后端知识- 收藏集 - 掘金

    摘要:常见的八大排序算法,他们之间关系如下被人忽视的面向对象的六大原则后端掘金前言作为文集的第一篇,我觉得有必要介绍一下大概的写作规划。 Java多线程干货系列—(四)volatile关键字| 掘金技术征文 - 掘金原本地址:Java多线程干货系列—(四)volatile关键字博客地址:http://tengj.top/ 前言 今天介绍下volatile关键字,volatile这个关键字可能...

    Youngdze 评论0 收藏0

发表评论

0条评论

lewinlee

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<