Python 装饰器、嵌套函数、高阶函数

装饰器

装饰器 = 高阶函数 + 嵌套函数

定义

用来装饰其他函数的,即为其他函数添加特定功能的函数,即装饰器本质也是一个函

装饰器函数的两个基本原则

  • 装饰器不能修改被装饰函数的源码
  • 装饰器不能修改被装饰函数的调用方式

函数即变量

函数即变量的思想:即函数也可以像变量一样赋值使用,如

def foo():
    print('in foo')

a = foo  # 把函数赋值给一个变量
a()  # 调用这个变量,即和调用函数一样的效果

高阶函数

高阶函数,符合下列条件之一的函数就是高阶函数:

(1)接收一个函数作为形参

(2)返回一个函数(return 函数)

高阶函数的两个条件对编写装饰器的意义:

(1)接收函数作为形参 -> 可以不改变被装饰函数的代码的前提下增加功能

def foo():
    print('in foo')

# gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
def gf(func):
    # func = foo
    # 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
    func()  # 通过变量调用函数

    # 高阶函数除了可以调用 foo 函数,即执行 foo 函数功能之外,可以附加执行一些功能,如打印函数地址
    # 这接近于装饰器的功能:在不改变别的函数源码的前提下给其添加附加功能
    # 但高阶函数不是装饰器,因为满足条件一:不改变被装饰函数的源码,但不满足条件二:不改变被装饰函数的调用方式
    print(func)

gf(foo)  # 改变了函数 foo 的调用方式

(2)返回一个函数(return 函数) -> 可以不改变被装饰函数的调用方式

def foo():
    print('in foo')

# gf 函数接收一个函数作为变量,故 gf 是一个高阶函数
def gf(func):
    # func = foo
    # 等同于把一个函数赋值给一个变量 func,然后通过变量 func 调用函数
    return func
    
# 可以不改变 foo 函数的调用方式
foo = gf(foo)
foo()

但是可以看出高阶函数不能同时满足两个条件,所以高阶函数不是真正的装饰器

嵌套函数

通过 def 关键字定义在另一个函数中的函数叫嵌套函数,如

def foo():
    print('in foo')
    
    def boo():
        print('in boo')

装饰器实现

装饰器 = 高阶函数 + 嵌套函数,所以可以得到如下代码

import time


def timer(func):
    # func = foo
    # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    def gf():
        start_time = time.time()
        func()  # => foo()
        end_time = time.time()
        print('func 运行时间为:', end_time - start_time)

    # 返回一个函数,可以不改变被嵌套函数的调用方式
    return gf

def foo():
    time.sleep(2)
    print('in foo')

foo = timer(foo)
foo()  # 函数调用方式不变,又多了打印其运行时间的附加功能

@的形式是Python 提供的装饰器的语法糖,等价于 foo = timer(foo)

import time

def timer(func):
    # func = foo
    # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    def gf():
        start_time = time.time()
        func()  # => foo()
        end_time = time.time()
        print('func 运行时间为:', end_time - start_time)

    # 返回一个函数,可以不改变被嵌套函数的调用方式
    return gf

@timer  # => foo = timer(foo)
def foo():
    time.sleep(2)
    print('in foo')

# foo = timer(foo)
foo()  # 函数调用方式不变,又多了打印其运行时间的附加功能

装饰器的运行过程

可以通过给上述的每一行代码加上断点,运行 debug 就可以看到装饰器的运行过程

装饰器编写的基本套路

  1. 定义一个接收函数的高阶函数
  2. 在高阶函数中定义一个嵌套函数,在该嵌套函数中:
    1. 封装想要添加的功能代码
    2. 调用作为参数传入的函数名
  3. 高阶函数返回该嵌套函数

常见的几种装饰器类型

被装饰函数带参数

带一个参数

import time

def timer(func):
    # func = foo
    # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    def gf(name):
        start_time = time.time()
        func(name)  # => foo(name)
        end_time = time.time()
        print('func 运行时间为:', end_time - start_time)

    # 返回一个函数,可以不改变被嵌套函数的调用方式
    return gf

@timer  # => foo = timer(foo) => foo = gf
def foo(name):
    time.sleep(2)
    print('in foo', name)

# foo = timer(foo)  # => foo = gf
foo('python')  # => gf('python')

带多个参数

import time

def timer(func):
    # func = foo
    # 接收一个函数,可以在不改变函数源码的情况下为其增加附加功能
    # *args, **kwargs 可以接收任意的参数
    def gf(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)  # => foo(*args, **kwargs)
        end_time = time.time()
        print('func 运行时间为:', end_time - start_time)

    # 返回一个函数,可以不改变被嵌套函数的调用方式
    return gf

@timer  # => foo = timer(foo) => foo = gf
def foo(name, age):
    time.sleep(2)
    print('in foo', name, age)

# foo = timer(foo)  # => foo = gf
foo('python', 18)  # => gf('python', 18)

装饰器本身带参数

import time

def timer(timer_type):
    print(timer_type)
    def outer(func):
        # func = foo
        # *args, **kwargs 可以接收任意的参数
        def gf(*args, **kwargs):
            start_time = time.time()
            func(*args, **kwargs)  # => foo(*args, **kwargs)
            end_time = time.time()
            print('func 运行时间为:', end_time - start_time)

        # 返回一个函数,可以不改变被嵌套函数的调用方式
        return gf
    return outer

# 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
# 所以 @timer('装饰器参数') 已经变成了 @outer
@timer('装饰器参数')  # => @outer
def foo(name, age):
    time.sleep(2)
    print('in foo', name, age)

# foo = timer('装饰器参数')(foo)  # => foo = outer(foo)  # => foo = gf
foo('python', 18)  # => gf('python', 18)

# 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程

被装饰函数带返回值

import time

def timer(timer_type):
    print(timer_type)
    def outer(func):
        # func = foo
        # *args, **kwargs 可以接收任意的参数
        def gf(*args, **kwargs):
            start_time = time.time()
            res = func(*args, **kwargs)  # => foo(*args, **kwargs)
            end_time = time.time()
            print('func 运行时间为:', end_time - start_time)

            return res

        # 返回一个函数,可以不改变被嵌套函数的调用方式
        return gf
    return outer

# 此时 timer('装饰器参数') 带有小括号,表示已经调用了 timer 函数,那么得到了 timer 函数的返回值 outer
# 所以 @timer('装饰器参数') 已经变成了 @outer
@timer('装饰器参数')  # => @outer
def foo(name, age):
    time.sleep(2)
    print('in foo', name, age)
    return 'foo 返回了'

# foo = timer('装饰器参数')(foo)  # => foo = outer(foo)  # => foo = gf
res = foo('python', 18)  # => gf('python', 18)
print(res)

# 提示:把每一行代码打断点调试可以更好的理解装饰器的运行过程

你可能感兴趣的:(Python高阶,python,开发语言)