python装饰器(Decorator)-从基本原理到实践操作

Python的面向对象

python面向对象,函数也是对象

要理解装饰器,首先明确python是面向对象的,函数也是对象。python中的函数也是对象,也可以像对象一样来被操作,可以传递,可以赋值,可以删除等等。

def speaking(words="Hello World"):
    print(words.capitalize()+'!')
speaking()
speaking(words='Good Morning')

#函数作为一个对象,赋值给另外一个变量
talking=speaking()
talking
#也可以这样来赋值
shouting=speaking
shouting(words="Shut up")

#可以被传递参数和赋值
def say(howToSay,what):
    howToSay(words=what)

say(speaking,"what")

def wish(HowtoSay):
    HowtoSay
wish(speaking())

输出结果为

Hello world!
Good morning!
Hello world!
Shut up!
What!
Hello world!

python中,一个函数可以定义在另外一个函数内部

python中,一个函数可以定义在另外一个函数内部

def wish():
    def speaking(words="Hello"):
        print(words.lower()+'!')
    speaking(words="Good")
    a=0
    print(a)
wish()

运行结果是

good!
0

这里可以看到:

  • speaking()和a是局部变量,不可以被作用域外调用
  • python中的函数可以被赋值给另一个变量。
  • python中的函数可以非常灵活地在各种位置被定义,包括另一个函数内部。
    因而我们甚至可以把一个函数作为另一个函数的返回值。如下面的例子所示。
def wish(return_type='morning'):
    def speaking(words="Good morning"):
        print(words.capitalize()+'!')
    def shouting(words="Yes"):
        print(words.lower()+' sir!')
    if return_type=='morning':
        return speaking
    else:
        return shouting

fun=wish('sir')
print(fun)
fun('No')

输出为

<function wish.<locals>.shouting at 0x00000240A9367048>
no sir!

同样,函数可以作为另外一个函数的输入参数,如

def speaking(words="Good morning"):
    return words.capitalize() + '!'
def function_as_arguments(func):
    print("function as arguments")
    print(func)
function_as_arguments(speaking())

输出结果为:

function as arguments
Good morning!

什么是装饰器

本质而言,python中的装饰器其实只是对其所装饰的函数的一层额外包装。其实现方法与商务的代码逻辑类似,即接受一个函数作为输入,然后定义另外一个包装函数在其执行前后加入另外一些逻辑,最终返回这个包装函数。在装饰器中,我们可以完全不修改原有函数的情况下,执行所装饰的函数之外另外包装一些别的代码逻辑。装饰器就是接受一个函数作为输入,并返回另外一个函数的函数。

一个基本的装饰器

其基本逻辑为:在一个装饰器函数中,我们首先定义另外一个包装函数,这个函数将负责在我们所要装饰的函数前后文中添加我们需要的代码逻辑(也就是将需要被装饰的函数包装起来)。然后在装饰器函数中,我们将这一包装函数作为返回值返回。下面是一个简单的装饰器函数。

def basic_decorator(func_to_decorate):
    #定义包装函数
    def the_wrapper_around_the_original_function():
        #被装饰的原始函数执行之前的逻辑
        print("Before the original funcation runs")

        #调用原始函数
        func_to_decorate()

        #被装饰的原始函数执行之后的逻辑
        print('After the original function runs')
    #返回当前装饰器函数中动态定义的包装函数
    return the_wrapper_around_the_original_function

下面展示如何使用这个装饰器函数

def funtiong_wewantto_decorate():
    print('This is a function that is going to be decorated, we can add additional execution logic without changing the function')

funtiong_wewantto_decorate()
# 我们只需要将`funtiong_wewantto_decorate`作为参数传入我们上面定义的装饰器函数中,就可以获得一个被包装过的新函数。
decorated_function=basic_decorator(funtiong_wewantto_decorate)
decorated_function()
print('-----***********')
#考虑到python中使用装饰器往往是为了在后文中完全用装饰过后的函数替代我们原本定义的函数,我们可以将装饰过后的函数赋值给原函数对应的变量名,从而在代码下文中实现永久替换。
funtiong_wewantto_decorate=basic_decorator(funtiong_wewantto_decorate)
funtiong_wewantto_decorate()

运行结果如下:


This is a function that is going to be decorated, we can add additional execution logic without changing the function
Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs
-----***********
Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs

这就是装饰器背后的逻辑,其表现和@注释的装饰器完全一样。

用@标志装饰器

常用的标志方法:

@basic_decorator
def funtiong_wewantto_decorate():
    print('This is a function that is going to be decorated, we can add additional execution logic without changing the function')

funtiong_wewantto_decorate()

运行结果:

Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs

多个装饰器的执行顺序

进一步,可以对一个函数使用多个装饰器。多层装饰器将被从里到外执行。即同一函数定义上方的装饰器,最上面一行的装饰器将被最后套用,而最下面一行的装饰器将被最先套用。示例如下:

def decorator_func1(func):
    def wrapper1():
        print("decorator_func1 before")
        func()
        print("decorator_func1 after")
    return wrapper1
def decorator_func2(func):
    def wrapper2():
        print("decorator_func2 before")
        func()
        print("decorator_func2 after")
    return wrapper2

# func()
# decorator_func1(decorator_func2(func))
@decorator_func1
@decorator_func2
def bj(sentence="bj"):
    print(sentence)
bj()
decorator_func1 before
decorator_func2 before
bj
decorator_func2 after
decorator_func1 after

装饰器的一个实际示例

如何使用装饰器,或者用装饰器能干什么。现在我们有两个装饰器,一个是能够自动给字符串增加斜体HTML tag,一个是能够自动增加黑体HTML tag。

def make_fold(func):
    def wrapper():
        return '<b>{}</b>'.format(func())
    return wrapper
#增加斜体tag的装饰器
def make_italic(func):
    def wrapper():
        return '<i>{}</i>'.format(func())
    return wrapper

@make_fold
@make_italic
def say():
    return "hello world"
print(say())
#上面等同于
def say():
    return "hello world"
say=make_fold(make_italic(say))
print(say())

运行结果如下,好好体会一下。python的装饰器本质上只是一个接受一个函数对象作为输入,在该函数前后增加一些新逻辑,然后返回包装过后的新函数对象的对象。

<b><i>hello world</i></b>
<b><i>hello world</i></b>

装饰器的进阶

有输入参数的函数的装饰器

对于有输入参数的函数,该如何定义适用于他们的装饰器。

def decorator_for_func_with_arguments(func_to_decorate):
    def wrapper(arg1,arg2):
        print("good morning, {} {}".format(arg1,arg2))
        func_to_decorate(arg1,arg2)
    return wrapper

@decorator_for_func_with_arguments
def print_full_name(first_name,last_name):
    print("I am {} {}".format(first_name,last_name))
print_full_name("Jorden","Henderson")

输出结果为:

good morning, Jorden Henderson
I am Jorden Henderson

由此可见,装饰器事实上返回了一个新的函数来代替我们需要装饰的函数,所以只要确保在装饰器中返回的新函数和原函数所接受的参数格式一致即可。

类方法的装饰器

类方法的装饰器,类方法事实上和函数一样,只是固定接受当前实例的引用作为第一个参数,即self。那么能够装饰类放的装饰器事实上也可以用与问题中一致的方法来实现,只不过要确保返回的函数所接受的第一个参数也是当前实例的引用self即可。

def decorator_for_instance_method(method_to_decorate):
    def wrapper(self,bonus):
        bonus=bonus*3
        print("before")
        method_to_decorate(self,bonus)
        print("after")

    return wrapper

class Salary(object):
    def __init__(self):
        self.base=1000
    @decorator_for_instance_method
    def total_compensation(self,bonus):
        print("the total compensation is {}".format(self.base+bonus))

salary_instance=Salary()
salary_instance.total_compensation(500)

运行结果如下:

before
the total compensation is 2500
after

类似,我们可以用python中的*args,**kwargs来实现一个能够装饰接受任意书目参数函数的装饰器。

def decorator_passing_arbitrary_arguments(function_to_decorate):
    def wrapper_with_arbitrary_arguments(*args, **kwargs):
        print('Received arguments as following')
        print(args)
        print(kwargs)

        function_to_decorate(*args, **kwargs)

    return wrapper_with_arbitrary_arguments

@decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print('This function does not have any argument')

function_with_no_argument()
# output:
# Received arguments as following
# ()
# {}
# This function does not have any argument

@decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print('This function has arguments')

function_with_arguments(1,2,3)
# output:
# Received arguments as following
# (1, 2, 3)
# {}
# This function has arguments

@decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, name)
    print('{}, {}, {}'.format(a, b, c))
    print('{}'.format(name))

function_with_named_arguments(1, 2, 3, name='robot')
# output:
# Received arguments as following
# (1, 2, 3)
# {'name': 'robot'}
# 1, 2, 3
# robot

class Salary(object):
    def __init__(self):
        self.base = 666

    @decorator_passing_arbitrary_arguments
    def total_compensation(self, bonus):
        print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))

salary = Salary()
salary.total_compensation(2048)
# salary.total_compensation(2048)
# Received arguments as following
# (<__main__.Salary object at 0x1070b5f28>, 2048)
# {}
# Congrats! You got a total compensation of 10040

给装饰器传入参数

所谓装饰器其实就是接收一个函数作为输入,并返回另一个函数的函数。这种情况下,由于装饰器的函数签名已经固定,所以我们无法直接传入除输入函数之外的参数。在无法直接改变装饰器签名的情况下,我们需要采用一些别的办法来实现我们的目标——实现一个能够返回装饰器的函数来替装饰器接收参数,并使用闭包的方法来将这些参数传递到装饰器中。换句话说,我们需要一个装饰器工厂函数来替我们动态生成装饰器。

def decorator_maker():
    print("this is a factory generating decorators")
    def my_decorater(func):
        print("my decorator")
        def wrapper():
            print("this a wrapper")
            return func()
        return wrapper
    print("decorator created")
    return my_decorater

@decorator_maker()
def func():
    print("this is a function")

结果为:

this is a factory generating decorators
decorator created
my decorator
this a wrapper
this is a function

装饰器的最佳实践

装饰器的用法多种多样。举例来说,我们如果想要扩展一个第三方库中附带的函数的功能,但我们又无法修改该函数源代码的时候,我们就可以使用装饰器来实现这一目的。或者我们在debug的时候,为了避免对源代码进行多次修改,就可以用装饰器来附加我们想要的逻辑。换句话说,我们可以用装饰器实现所谓的“干修改”(Dry Change)。实际上,python自身也提供了一些常用的装饰器供大家调用,例如property,staticmethod,等等。与此同时,一些常用的python后端框架,例如Django及Pyramid也使用装饰器来管理缓存以及视图(view)访问权限等。另外,装饰器有时候也用来在测试中来虚构异步请求。

import time
import functools


def benchmark(func):
    """
    这是一个能够计算并打印一个函数运行时间的装饰器
    """
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print('{} completed in {} seconds'.format(func.__name__,  end_time - start_time))
        return res
    return wrapper


def logging(func):
    """
    这是一个能够附加log功能的装饰器
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print('{} executed with args: {} and kwargs: {}'.format(func.__name__, args, kwargs))
        return res
    return wrapper


def counter(func):
    """
    这是一个能够对函数被调用次数进行计数的装饰器
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print('{} has been called for {} times'.format(func.__name__, wrapper.count))
        return res
    wrapper.count = 0
    return wrapper


@c[添加链接描述](https://blog.csdn.net/Yaokai_AssultMaster/article/details/91552037)ounter
@logging
@benchmark
def reverse_string(string):
    return ''.join(reversed(string))


reverse_string('Tough times do not last, tough people do.')
# output:
# reverse_string completed in 3.814697265625e-06 seconds
# reverse_string executed with args: ('Tough times do not last, tough people do.',) and kwargs: {}
# reverse_string has been called for 1 times
# '.od elpoep hguot ,tsal ton od semit hguoT'

reverse_string('Two things are infinite: the universe and human stupidity; and I am not sure about the universe.')
# reverse_string completed in 5.9604644775390625e-06 seconds
# reverse_string executed with args: ('Two things are infinite: the universe and human stupidity; and I am not sure about the universe.',) and kwargs: {}
# reverse_string has been called for 2 times
# '.esrevinu eht tuoba erus ton ma I dna ;ytidiputs namuh dna esrevinu eht :etinifni era sgniht owT'

参考资料:
https://blog.csdn.net/Yaokai_AssultMaster/article/details/91552037
https://blog.csdn.net/Yaokai_AssultMaster/article/details/90730387

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值