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

栏目: Python · 发布时间: 6年前

内容简介:“Python有什么好学的”这句话可不是反问句,而是问句哦。主要是煎鱼觉得太多的人觉得Python的语法较为简单,写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简单)。于是我便记录一下,如果要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其他语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。

“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有什么好学的》之修饰器

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

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

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

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

  1. 工作量大
  2. 代码变得臃肿复杂
  3. 原代码有多处调用了这些函数,可以会升级不完全

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

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

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

以上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()

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

上面的例子中的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)

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

就是参数的问题。这个时候,我们把修饰器函数改得通用一点即可,其中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')

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

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

带参数的修饰器

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

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

如,我这次用这个修饰器是把时间日志打到 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)

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

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

修饰器类

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

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

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

你们猜猜结果怎么样?

先这样吧

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


以上所述就是小编给大家介绍的《《Python有什么好学的》之修饰器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First JavaScript Programming

Head First JavaScript Programming

Eric T. Freeman、Elisabeth Robson / O'Reilly Media / 2014-4-10 / USD 49.99

This brain-friendly guide teaches you everything from JavaScript language fundamentals to advanced topics, including objects, functions, and the browser’s document object model. You won’t just be read......一起来看看 《Head First JavaScript Programming》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具