Python编程-二万字浅谈装饰器原理与装饰器设计模式和函数式编程案例讲解

Python编程-浅析装饰器原理与装饰器设计模式和函数式编程案例讲解

本文制作时基于Python3.11.8Python3.12.1,存在谬误,请联系修改,希望对你有所帮助

什么是函数式编程

函数式编程(Functional Programming)是一种编程范式,它将计算视为数学函数的计算,并避免了状态改变以及可变数据。在函数式编程中,函数被视为一等公民,意味着它们可以像其他数据类型一样被传递、返回和操作。

以下是函数式编程的一些主要特征:

  1. 纯函数(Pure Functions):纯函数指的是函数的输出仅由输入决定,不会对外部状态产生影响,也不依赖外部状态。相同的输入总是产生相同的输出,这种特性使得纯函数易于理解、测试和并行化。

  2. 不可变性(Immutability):在函数式编程中,数据是不可变的,即一旦创建就不可更改。这意味着一旦数据被创建,就不能被修改,而是通过创建新的数据来代替。

  3. 无副作用(Side-effect free):函数式编程尽量避免副作用,即对系统外部环境造成的影响。这意味着函数不会修改外部状态,包括修改全局变量、执行 I/O 操作等。

  4. 高阶函数(Higher-order Functions):函数式编程支持高阶函数,即函数可以作为参数传递给其他函数,或者从其他函数返回。

  5. 递归(Recursion):递归是函数式编程中常见的一种迭代方式,用于替代循环。

  6. 惰性计算(Lazy Evaluation):函数式编程支持惰性计算,即只有在需要时才会计算表达式的值,这种特性在处理无限数据结构时特别有用。

函数式编程的优势包括代码简洁、可读性强、易于测试和并行化。它在处理并发编程、大数据处理、分布式系统等方面具有很好的适用性。常见的函数式编程语言包括Haskell、Clojure、Scala、Erlang等。此外,现代编程语言如Python、JavaScript和Java等也提供了一些函数式编程的特性和支持。

什么是高阶函数

在Python中函数名可以作为参数进行传递,例如多线程中的方法包装:

def function_name():
    pass

thread = Thread(target=function_name)

由于函数名可以作为一个参数进行传递,于是python中存在将一个函数传递给另一个函数的函数被称为高阶函数:

def print_hello(hint_string) -> None:
    print('Hello '+ hint_string)


def start_func(other_func) -> None:
    other_func('World')


start_func(print_hello)

上述的start_func即为高阶函数,这个和PHP是较为类似的,也可以理解为C语言中的函数指针

高阶函数匿名函数使用案例

Lambda 表达式是 Python 中的一种匿名函数,它允许快速定义简单的函数,而不需要使用 def 定义函数的形式。Lambda 表达式的语法如下:

lambda arguments: expression

其中:

  • lambda:关键字,表示定义 lambda 表达式。
  • arguments:函数的参数,可以是零个或多个参数,与普通函数一样,但不允许使用默认参数值或可变参数。
  • expression:函数体,表示函数的返回值。

Lambda 表达式通常用于需要一个函数作为参数的情况,比如在高阶函数中,例如 map()filter()sorted() 等函数中,或者在需要临时定义一个简单函数的地方。

例如使用 Lambda 表达式定义一个简单的函数,计算两个数的和:

add = lambda x, y: x + y
print(add(3, 4))  # 输出:7

高阶函数map使用案例

map函数接收一个函数名和一个或多个可迭代对象,返回的是一个map对象,并且map对象实现了可迭代协议:

def calculate_func(num: int) -> int:
    return abs(num) ** 2

num_list: list[int] = [1, 2, 4, -5, -9]

res_list: list[int] = list(map(calculate_func, num_list))

print(res_list)
for i in map(calculate_func, num_list): # 借助可迭代协议进行迭代的方式
    print(i)

在上述操作中,map函数会将可迭代对象的每个元素逐个递交由第一个参数所指定的函数进行处理,然后返回一个map对象供我们操作

并且需要注意的是,除了第一个参数指定为函数名,后面的可迭代对象的参数将逐个以位置形参的形式传递给函数名:

def calculate_func(list1_var: int, list2_var: int) -> int:
    return list1_var + list2_var

num_list1: list[int] = [1, 2, 4, -5, -9]
num_list2: list[int] = [1, 7, 4, -6, -9]


res_list: list[int] = list(map(calculate_func, num_list1, num_list2))

print(res_list)

高阶函数reduce使用案例

位于functools中的reduce接收三个参数,第一个参数必须为两个参数的函数名;第二个参数必须是一个可迭代的对象;第三个参数用于指定初始值,它是一个可选值,未指定值时时使用可迭代对象的第一个参数为基准,指定后则以指定的值为基准值。reduce函数用来将函数运算的结果进行累计,即将函数运算的结果累计作为下一次运算的第一个参数,可迭代对象的下一个值将自动作为第二参数继续运算,直到可迭代对象迭代结束。

from functools import reduce

def calculate_func(num1: int, num2: int) -> int:
    return num1 + num2

num_list: list[int] = [1, 8, 4, -5, -9]

res_list: int = reduce(calculate_func, num_list)

print(res_list)

在上述代码中,未指定初始值,基准为传入列表的第一个值作为基准,进入函数作为第一参数,运算时列表的第二个元素作为函数的第二参数,下面我们看指定初始值的情况,(我这里使用了lambda表达式):

from functools import reduce

num_list: list[int] = [1, 8, 4, -5, -9]

res: int = reduce(lambda x, y: x+y, num_list, 25)

print(res)

高阶函数filter的使用案例

filter() 是 Python 中的一个内置函数,用于过滤序列(列表、元组、集合等)中的元素。filter() 函数接受一个函数和一个可迭代的序列作为参数,并返回一个由符合条件的元素组成的迭代器。该函数在每次迭代时,都会将序列中的元素传递给指定的函数,如果函数返回值为 True,则该元素将被保留,否则将被过滤掉。

from functools import reduce

def is_pos_neg_num(num: int) -> bool:
    if num > 0 :
        return True
    else :
        return False

num_list: list[int] = [1, 8, 4, -5, -9, 5, -8]

res = list(filter(is_pos_neg_num, num_list))

print(res)

还可以用来过滤字符串中的值,这里以去除空格为例:

from functools import reduce

original_string = 'Hello World Python Java CPP'

res: str = ''.join(list(filter(lambda x: False if x == ' ' else True, original_string)))

print(res)

高阶函数sorted使用案例

sorted() 是 Python 中的一个内置函数,用于对可迭代对象进行排序操作。它接受一个可迭代对象作为参数,并返回一个新的已排序的列表,sorted还有两个关键字参数:

  • key(可选):用于指定排序的比较函数。默认为 None,表示直接对元素进行比较排序。如果指定了 key,则 sorted() 函数将会以 key 函数的返回值作为排序的依据。
  • reverse(可选):指定排序的顺序,如果设置为 True,则降序排序;如果设置为 False(默认值),则升序排序。
words = ["banana", "apple", "orange", "grape", "kiwi"]

res = sorted(words, key=len, reverse=True)

print(res)

如上示例,我们以指定以字符串长度为比较依据,并且置为逆序

高阶函数偏函数使用案例

Python 中的偏函数(Partial Function)是指使用 functools.partial 函数对现有函数创建一个新的函数,固定该函数的部分参数。在需要多次调用同一个函数但又希望保持某些参数不变的情况下非常有用(使用函数默认值也可以实现)。

from functools import partial

def get_greeting(name, sentence):
    return 'Hello!!! ' + str(name) + ',' + str(sentence)

get_greeting_default = partial(get_greeting, sentence='You are the real IKUN')


print(get_greeting_default('kun'))
print(get_greeting_default('kunkun'))

使用此方法,更多时候是需要重写自己不方便修改的部分,例如在使用bytes对象转换时需要使用指定编码时,或使用int时指定转换进制:

gbk_bytes = partial(bytes, encoding='gbk')
binary_int = partial(int, base=2)

高阶函数闭包函数与使用案例

闭包(Closure)是函数式编程中的一个重要概念(闭包实质上也是一种高阶函数),也在 Python 中得到了支持。闭包指的是一个函数对象,该函数可以访问并操作其创建时所在的作用域中的变量,即使这些变量在函数被调用之后已经不存在了。在 Python 中,闭包通常由内部函数和外部函数组成。内部函数可以访问外部函数中定义的变量(一般定义闭包就是需要访问外部函数的变量或参数),即使外部函数已经执行完毕并且不再存在。

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(5)
print(closure(3))

在这个示例中,outer_function 是外部函数,它接受一个参数 x。内部函数 inner_function 可以访问外部函数 outer_function 中的变量 x。当调用 outer_function(5) 后,它返回了一个闭包 closure,此时 x 被赋值为 5。当调用 closure(3) 时,内部函数 inner_function 访问了外部函数中的变量 x,并且将 3 加上了 x,得到了 8

闭包的优点之一是它可以保持外部函数的状态,这使得它允许函数在每次调用时记住它所处的上下文,并且可以在之后的调用中使用这个上下文。

需要注意的是,闭包可能会导致内存泄漏,因为闭包中引用的外部变量会被保留,即使它们不再被使用。因此,应该小心使用闭包,并确保在不需要时适时释放资源。

利用闭包求解任意点到固定点之间的距离:

from typing import Callable
import math

def set_fixed_point(x1, y1) -> Callable[[int, int], float]:
    def get_distance(x2, y2) -> float:
        return math.sqrt((x1 - x2)**2 + (y1 - y2)**2)
    return get_distance

distance = set_fixed_point(0, 0)
print(distance(2, 2))

多个内部函数闭包使用案例

Python允许在一个函数中创建多个内部函数。在 Python 中,函数是一级对象,因此可以像任何其他对象一样在函数内部进行定义、传递和返回。需要注意的是:每个内部函数都会占用内存空间,如果定义了大量的内部函数,可能会导致内存占用过多

当在一个函数中定义了多个内部函数时,可以通过调用外部函数来获取对应的内部函数。每个内部函数在外部函数被调用时都会重新创建,因此每次调用外部函数都会得到新的内部函数实例,并且它们之间是相互独立的,互不影响。

def outer_function():
    def inner_function1():
        return "This is inner function 1"
    
    def inner_function2():
        return "This is inner function 2"
    
    return inner_function1, inner_function2

# 调用外部函数获取内部函数
func1, func2 = outer_function()

# 分别调用内部函数
print(func1())  
print(func2()) 

在这个示例中,outer_function 中定义了两个内部函数 inner_function1inner_function2。通过调用 outer_function,我们得到了对应的内部函数实例 func1func2。然后,我们可以像普通函数一样调用这些内部函数。

多重闭包函数使用案例

既然闭包允许函数并列成为内部函数,自然套娃也是可以的:

def outer_function():
    def inner_function1():

        def inner_function2():
            print("This is inner function 2")

        print("This is inner function 1")
        return inner_function2
    
    return inner_function1


func1 = outer_function()
func2 = func1()
func2()

这一点在多重装饰器上将会体现出来

使用闭包添加新的功能

在项目维护与功能添加中,如果要获取一些关键节点的信息,直接修改已有的项目代码显然是不够方便的,此时我们可以使用闭包来更新功能:

from typing import Callable
from time import asctime

def running_function() -> str:
    print("This is running function")

def create_logs(func: Callable) -> None:
    with open('log.txt', 'a', encoding='utf-8') as file:
        file.write(str(func.__name__) + '  ' + asctime() + '\n')

def outer_function(func) -> Callable[[None], str]:
    def inner_function() -> None:
        create_logs(func)
        func()

    return inner_function

func_start = outer_function(running_function)()

在 Python 中,__name__ 是一个特殊的内置变量,它用于获取当前模块的名称。当 Python 文件作为主程序直接执行时,__name__ 的值为 '__main__';而当 Python 文件被导入为模块时,__name__ 的值为该模块的名称。这个特性常用于将一些代码块标记为主程序或模块中的一部分,以便在需要时能够灵活地执行或导入模块,而不会对整个脚本产生副作用。

需要注意的是,__name__可以用于获取当前模块的名称,但不仅限于获取模块名称,还可以用于其他一些情况,如获取函数名等。t下面是用于模板渗透时的常见方式:

获取内容使用方法
模块名__name__
函数名function.__name__
类名self.__class__.__name__
方法名method.__name__
类的模块名self.__module__

闭包函数的作用域特性

闭包函数的作用域问题是指内部函数(闭包函数)可以访问外部函数的变量,而外部函数无法直接访问内部函数的变量。这种作用域链的特性使得闭包函数可以在其定义时捕获外部作用域的变量,并在稍后的调用中保持对这些变量的引用。

具体来说,当内部函数引用外部函数的变量时,Python 解释器会在内部函数的作用域中查找这些变量。如果在内部函数的作用域中找不到对应的变量,则会继续向上一级作用域查找,直到找到或者到达全局作用域。这样就形成了闭包函数可以访问外部函数作用域中的变量的效果。闭包函数的作用域具有以下几个特点:

  1. **捕获外部变量:**闭包函数可以在其定义时捕获外部作用域的变量,并在稍后的调用中保持对这些变量的引用,即使外部函数已经执行完毕,这些变量依然存在于闭包函数的作用域中。

  2. **保持状态:**由于闭包函数可以访问外部函数作用域中的变量,并且这些变量在闭包函数调用之间保持不变,因此闭包函数可以用来实现状态的保持,例如计数器、缓存等。

  3. **避免全局污染:**使用闭包函数可以避免将变量声明为全局变量,从而减少了全局作用域的变量污染和命名冲突的可能性。

即闭包中的内部函数中的字段对于外部是不可见的,内部函数却可以捕获外面的字段,这保证了内部数据的完整性,不过需要注意的是,内部函数一般不用于修改外部内容

编程语言中的的对象级别

在 Python 中,常见的对象级别大致上可以分为两个(实质上并无明确规定)

一级对象的特征

一级对象(First-class object)是指在编程语言中,能够作为普通对象使用的实体。具有一级对象特性的对象可以被传递给函数、存储在变量中、作为函数的返回值、放在数据结构中等。在 Python 中,函数、类、模块、数字、字符串、列表、字典等都是一级对象。

  1. 可赋值性(Assignability):一级对象可以被赋值给变量,并且可以存储在数据结构中。
  2. 作为函数参数(Passability as arguments):一级对象可以作为函数的参数传递给其他函数。
  3. 作为函数返回值(Returnability from functions):一级对象可以作为函数的返回值返回给调用者。
  4. 可以存储在数据结构中(Storability in data structures):一级对象可以被存储在列表、字典、集合等数据结构中。

二级对象的特征

二级对象通常指的是某些限制了一定程度使用和支持的对象在一些编程语言中,二级对象可能不能作为函数的参数、返回值或存储在数据结构中,或者有其他限制。在 Python 中,常见的二级对象可能是一些特殊的内置类型或对象,例如文件对象、生成器对象等,它具有以下特点:

  1. 部分支持作为函数参数和返回值:某些对象可能不支持作为函数的参数或返回值,或者只能作为参数而不能作为返回值。
  2. 部分支持存储在数据结构中:某些对象可能不能被直接存储在数据结构(例如列表、字典)中,或者只能在特定的上下文中存储。
  3. 有限的操作和功能:某些对象可能只支持有限的操作和功能,而不能像一级对象那样支持全部的操作。
  4. 具有特定的使用限制或约束:某些对象可能有特定的使用限制或约束,可能需要满足特定的条件才能使用或进行操作。

装饰器的概念与自定义方式

装饰器(Decorator)是一种Python语法特性,它允许在不修改原始函数或类定义的情况下,动态地添加功能或修改其行为。装饰器本质上是一个函数,它期望接受一个函数作为输入,并返回一个新的函数作为输出。需要注意的是,装饰器函数必须定义在被装饰的函数被之前

装饰器通常用于以下几个方面:

  1. **代码重用:**可以使用装饰器将一些通用的功能分离出来,并将其应用于多个函数或方法,从而实现代码重用。

  2. **日志记录:**通过装饰器可以在函数执行前后添加日志记录功能,记录函数的调用时间、参数、返回值等信息。

  3. **权限检查:**装饰器可以用于检查用户权限,例如验证用户是否具有执行特定函数的权限。

  4. **性能测试:**可以使用装饰器来测量函数的执行时间,以便进行性能分析和优化。

  5. **异常处理:**装饰器可以用于捕获函数中的异常,并进行适当的处理或记录。

装饰器的语法使用 @ 符号,将装饰器函数放在被装饰函数之前。例如:

def my_decorator(function):
    def wrapper():
        print("Something is happening before the function is called.")
        function()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello! World")

say_hello()

在上面的例子中,say_hello 函数被 my_decorator 装饰器修饰,当调用 say_hello 函数时,实际上是调用了装饰器中的 wrapper 函数,从而实现了在函数执行前后添加额外的功能。

装饰器与闭包的关系

装饰器和闭包在 Python 中常常一起使用,它们之间有一定的联系,但也有一些区别。下面是它们之间的关系:

  1. **装饰器使用闭包:**装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。通常情况下,装饰器内部会定义一个嵌套函数,也就是闭包,在这个闭包中可以访问外部函数的变量和参数。这个闭包函数通常用来包装原始函数,以便添加额外的功能。

  2. **闭包不一定是装饰器:**闭包是指内部函数可以访问外部函数的作用域,形成了一个封闭的作用域。闭包并不一定与装饰器结合使用,它可以用于各种目的,如延迟执行、保存状态等。

  3. **装饰器提供了一种更方便的语法来使用闭包:**使用装饰器可以更方便地将闭包应用于函数上,而不需要显式地调用闭包函数。装饰器的语法简洁明了,更符合 Pythonic 风格。

  4. **装饰器可以串联使用多个闭包:**由于装饰器可以叠加使用,一个函数可以被多个装饰器修饰,这样就形成了多个闭包嵌套的情况,这为函数添加多个不同的功能提供了便利。

装饰器的执行逻辑

从装饰器的概念来讲,本质上是闭包函数多了一个自动执行的过程。即使我们不执行传递进入的函数,不过被返回的内部函数将会在返回后被执行,正如下面的代码:

def decorator(func):
    print('This function is started: ', decorator.__name__)
    def inner_func1():
        print('This function is started: ', inner_func1.__name__)
    return inner_func1

@decorator
def func():
    print('This function is started: ', func.__name__)

func()

我们将的到如下运行结果:

This function is started:  decorator
This function is started:  inner_func1

即装饰器的实质调用链在此为decorator(func)(),我们去掉print内容再来观察进行装饰时Python虚拟机的字节码:

2           2 LOAD_CONST               0 (<code object decorator at 0x0000023D0A57D7C0, file "<string>", line 2>)
            4 MAKE_FUNCTION            0
            6 STORE_NAME               0 (decorator)

7           8 LOAD_NAME                0 (decorator)

8           10 LOAD_CONST              1 (<code object func at 0x0000023D0A355470, file "<string>", line 7>)
            12 MAKE_FUNCTION           0

7           14 PRECALL                 0
            18 CALL                    0

8           28 STORE_NAME              1 (func)

Python虚拟机依次进行了以下操作:

  1. LOAD_CONST: 将常量加载到栈顶。在这里,常量 <code object decorator at 0x0000023D0A57D7C0, file "<string>", line 2> 被加载到栈顶。
  2. MAKE_FUNCTION: 使用栈顶的常量创建一个函数对象,并将其推送到栈顶。在这里,栈顶的常量是之前加载的 <code object decorator at 0x0000023D0A57D7C0, file "<string>", line 2>
  3. STORE_NAME: 将栈顶的函数对象存储到名为 “decorator” 的变量中。
  4. LOAD_NAME: 将名为 “decorator” 的变量加载到栈顶。
  5. LOAD_CONST: 再次加载一个常量到栈顶。在这里,常量 <code object func at 0x0000023D0A355470, file "<string>", line 7> 被加载到栈顶。
  6. MAKE_FUNCTION: 创建一个函数对象并将其推送到栈顶。与之前类似,栈顶的常量是 <code object func at 0x0000023D0A355470, file "<string>", line 7>
  7. PRECALL: 进行函数调用之前的预调用操作。
  8. CALL: 执行函数调用操作。
  9. STORE_NAME: 将函数调用的结果存储到名为 “func” 的变量中。

则我们可以得出结论,在上述的代码中:

@decorator实质上执行的过程为在定义函数时执行了类似于func = decorator(func)的操作

多重装饰器包装方法

使用多个装饰器进行包装时,直接在被包装函数前以多个@进行引入即可,如下示例:

def my_decorator_one(function):
    def wrapper_one():
        print("Something is happening before the function is called.", '---by decorator one')
        function()
        print(function.__name__ + ' is started')
        print("Something is happening after the function is called.",  '---by decorator one')
    return wrapper_one

def my_decorator_two(function):
    def wrapper_two():
        print("Something is happening before the function is called.", '---by decorator two')
        function()
        print(function.__name__ + ' is started')
        print("Something is happening after the function is called.", '---by decorator two')
    return wrapper_two

@my_decorator_one
@my_decorator_two
def say_hello():
    print("Hello! World")

say_hello()

需要注意的是,运行后的结果:

Something is happening before the function is called. ---by decorator one
Something is happening before the function is called. ---by decorator two
Hello! World
say_hello is started
Something is happening after the function is called. ---by decorator two
wrapper_two is started
Something is happening after the function is called. ---by decorator one

可见程序是先执行的第一个装饰器,然后将第二个装饰器作为了第一个装饰器装饰的方法,然后第二装饰器装饰了被包装方法,即出现了嵌套的装饰器,即多重闭包

包装需要参数的函数或方法

与定义闭包函数时是类似的,我们要想包装需要参数的方法或函数,只需要将装饰器内部的函数定义为接收对应参数数量即可

def decorator(function):
    def inner_function(*args):
        print('This is inner of decorator')
        function(*args)
    return inner_function


@decorator
def connect_two_string(string1, string2):
    print(string1 + string2)

connect_two_string('Hello', 'World!!!')

向装饰器传递额外的参数

不难看出,装饰器的本质上依然是闭包函数,那么既然是函数,我们就可以为其传递参数,并在其中使用参数或进行更加复杂的操作

def decorator(*call_word):
    print(decorator.__name__ + ' is started')
    def inner_function(function):
        print(inner_function.__name__ + ' is started')
        def core_function(*args):
            print(core_function.__name__ + ' is started')
            function(*args)
        print(inner_function.__name__ + ' ' + str(call_word))
        return core_function
    return inner_function 


@decorator('Welcome to Python!!!')
def connect_two_string(string1, string2):
    print(string1 + string2)

connect_two_string('Hello', 'World!!!')

运行结果如下:

decorator is started
inner_function is started
inner_function ('Welcome to Python!!!',)
core_function is started
HelloWorld!!!

即在我们需要在装饰器使用额外的数据时,我们需要使用多重闭包,在第一层闭包中返回一个装饰器,所以上述代码的调用链应该是这样的:

decorator('Welcome to Python!!!')(connect_two_string)('Hello', 'World!!!')

字节码中也是这样操作的:

0           0 RESUME                   0

2           2 LOAD_CONST               0 (<code object decorator at 0x000002D462405F00, file "<string>", line 2>)
            4 MAKE_FUNCTION            0
            6 STORE_NAME               0 (decorator)

14           8 PUSH_NULL
            10 LOAD_NAME                0 (decorator)
            12 LOAD_CONST               1 ('Welcome to Python!!!')
            14 PRECALL                  1
            18 CALL                     1

15          28 LOAD_CONST               2 (<code object connect_two_string at 0x000002D462411C50, file "<string>", line 14>)
            30 MAKE_FUNCTION            0

14          32 PRECALL                  0
            36 CALL                     0

15          46 STORE_NAME               1 (connect_two_string)

18          48 PUSH_NULL
            50 LOAD_NAME                1 (connect_two_string)
            52 LOAD_CONST               3 ('Hello')
            54 LOAD_CONST               4 ('World!!!')
            56 PRECALL                  2
            60 CALL                     2
            70 POP_TOP
            72 LOAD_CONST               5 (None)
            74 RETURN_VALUE

在进行装饰的过程中多了一步:12 LOAD_CONST 1 ('Welcome to Python!!!'),而后在闭包的第二层,也就是装饰器所在处执行了以下内容,返回了一个装饰器:

15          28 LOAD_CONST               2 (<code object connect_two_string at 0x000002D462411C50, file "<string>", line 14>)
            30 MAKE_FUNCTION            0

14          32 PRECALL                  0
            36 CALL                     0

15          46 STORE_NAME               1 (connect_two_string)

装饰器的嵌套行为

既然闭包都能嵌套,那么装饰器不能进行嵌套,可太不合理了,不过这样可能会引起代码逻辑过于混乱,不建议尝试,有时显得低效且冗余:

def decorator_one(func):
    def inner_func(x, y):
        func(abs(x), abs(y))
    return inner_func

def decorator_two(func):
    @decorator_one
    def inner_func(x, y):
        func(x-1, y-1)
    return inner_func

@decorator_two
def sum(x, y):
    print(x+y)

sum(-6, 6)

实现智能装饰器案例讲解

经过上述的内容,我们会注意到一个问题,装饰器大致分为三种调用情况:

@decorator	# 直接装饰函数
@decorator()	# 装饰器本身使用了参数默认值
@decorator(..., ...)	# 装饰器传递了所有或合法参数

怎样实现在三种情况下都可以使用同一个装饰器来进行装饰呢?其实智能装饰器就是实现了情况判断的闭包函数

def decorator(*args, **kwargs):
    # 处理装饰器参数
    if len(args) == 1 and callable(args[0]): 
        func = args[0]
        def wrapper(*func_args, **func_kwargs):
            print('#' * 20)
            result = func(*func_args, **func_kwargs)
            print('#' * 20)
            return result
        return wrapper
    else:
        # 如果装饰器本身使用了参数默认值,或传递了参数,则在这里处理
        def actual_decorator(func):
            def wrapper(*func_args, **func_kwargs):
                print('-' * 20)
                print("Decorator args:", args)
                print("Decorator kwargs:", kwargs)
                result = func(*func_args, **func_kwargs)
                print('-' * 20)
                return result
            return wrapper
        return actual_decorator

# 使用装饰器来装饰函数
@decorator
def function():
    print("Inside function")

@decorator()  # 装饰器本身使用了参数默认值
def function_with_default():
    print("Inside function_with_default")

@decorator("arg1", key="value")  # 装饰器传递了所有或部分合法参数
def function_with_args():
    print("Inside function_with_args")

# 调用
function()
print()
function_with_default()
print()
function_with_args()

wraps装饰器使用案例

在Python中,wraps装饰器是一个用于创建装饰器的辅助函数,通常与装饰器一起使用以保留原始函数的元数据(例如函数名称、文档字符串、参数签名等)。这对于调试和日志记录非常有用,因为它能够保留原始函数的信息,而不是让被装饰的函数看起来像是另一个函数。

wraps装饰器位于functools模块中。通常,编写一个装饰器会在内部定义一个包装函数,并在这个包装函数内部对被装饰的函数进行一些操作。使用@wraps装饰器来装饰这个包装函数,这样可以确保原始函数的元数据被正确传递到包装函数。需要注意的是,使用它时必须传入指定的函数名作为参数,这也表明了其使用位置必须是装饰器中使用外部函数的内部函数,而不是装饰器函数本身:

from functools import wraps

def decorator(*call_word):
    print(decorator.__name__ + ' is started')
    def inner_function(function):
        print(inner_function.__name__ + ' is started')
        @wraps(function)
        def core_function(*args):
            print(core_function.__name__ + ' is started')
            function(*args)
        print(inner_function.__name__ + ' ' + str(call_word))
        return core_function
    return inner_function 


@decorator('Welcome to Python!!!')
def connect_two_string(string1, string2):
    print(string1 + string2)

connect_two_string('Hello', 'World!!!')

此时得到运行结果中,获取的core_function已经变成了外部函数名:

decorator is started
inner_function is started
inner_function ('Welcome to Python!!!',)
connect_two_string is started
HelloWorld!!!

什么是Python元数据

在Python中,元数据是指与对象相关的描述性信息,它包括但不限于以下内容:

  1. 函数名称(__name__:函数的名称。
  2. 模块名称(__name__:模块的名称。
  3. 文档字符串(__doc__:函数、类、模块或方法的文档字符串,提供了有关对象的描述和说明。
  4. 参数签名(__annotations__:函数或方法的参数和返回值的注释。
  5. 函数参数列表(__code__.co_varnames:函数的参数列表。
  6. 函数字节码(__code__.co_code:函数的字节码指令序列。
  7. 类名称(__name__:类的名称。
  8. 类文档字符串(__doc__:类的文档字符串,提供了有关类的描述和说明。
  9. 类属性和方法(__dict__:类的属性和方法字典。
  10. 模块级别的全局变量和函数(globals():模块中定义的全局变量和函数。
  11. 类级别的方法(__dict__:类中定义的方法,存储在类的字典中。
  12. 类继承关系(__bases__:类的直接父类。
  13. 模块级别的导入信息(__path__, __file__等):模块的导入路径、文件路径等信息。

利用Callable特性实现装饰器类

在Python中,callable是一个特性,用于检查一个对象是否可以被调用(即是否可作为函数调用)。如果一个对象是可调用的,则可以像调用函数一样使用它,通过在对象名后面加上一对圆括号,并且可以传递参数给它。在Python中,函数、类(如果定义了 __call__ 方法)、类的实例(如果定义了 __call__ 方法)以及其他一些对象都是可调用的。通过使用内置函数 callable(),你可以检查对象是否是可调用的。当你调用 callable() 并将一个对象作为参数传递给它时,如果该对象是可调用的,它将返回 True,否则返回 False

from functools import wraps

class CustomDecorator:
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):
        @wraps(func)
        def wrapped_func(*args, **kwargs):
            print("Decorator arguments:", self.arg1, self.arg2)
            result = func(*args, **kwargs)
            print('This is function: ', wrapped_func.__name__)
            return result
        return wrapped_func


@CustomDecorator("Hello", "World")
def my_function():
    print("Inside the function")
    
if callable(CustomDecorator):
    my_function()

设计模式之装饰器模式

所谓装饰,就是不能更改被装饰对象、类或者函数的原有的行为,在这些原有的行为上,添加一些扩展行为,来实现装饰的目的。

在设计模式中,装饰器模式是一种通用的解决方案,它的思想可以被用于设计实现中,但实际上并不一定需要使用 Python 中的装饰器语法来实现。

装饰器模式是一种结构型设计模式,它允许动态地为一个对象添加新的功能,而无需改变其原始类的结构。这种模式通过将对象放入一个包装器中来实现,该包装器包含了要添加的额外功能。在装饰器模式中,通常会有以下角色:

  1. 组件接口(Component Interface):定义了被装饰对象和装饰器共同实现的接口或抽象类。它是被装饰对象和装饰器的共同父类。

  2. 具体组件(Concrete Component):实现了组件接口,它是被装饰的原始对象。

  3. 装饰器(Decorator):也实现了组件接口,它包含了一个对组件的引用,并且可以通过该引用动态地添加额外的功能。

  4. 具体装饰器(Concrete Decorator):扩展了装饰器类,添加了具体的附加功能。

考虑一个简单的示例:假设我们有一个 Coffee 类,代表一杯咖啡,它有一个方法 cost() 来计算咖啡的价格。我们希望能够动态地为咖啡添加额外的配料,如奶油、糖浆等,而不需要修改 Coffee 类本身。

# 组件接口
class Coffee:
    def cost(self):
        pass

# 具体组件
class SimpleCoffee(Coffee):
    def cost(self):
        return 5

# 装饰器
class CoffeeDecorator(Coffee):
    def __init__(self, decorated_coffee):
        self.decorated_coffee = decorated_coffee

    def cost(self):
        return self.decorated_coffee.cost()

# 具体装饰器
class Milk(CoffeeDecorator):
    def cost(self):
        return self.decorated_coffee.cost() + 2

class Sugar(CoffeeDecorator):
    def cost(self):
        return self.decorated_coffee.cost() + 1

在这个示例中,Coffee 是组件接口,SimpleCoffee 是具体组件,CoffeeDecorator 是装饰器,而 MilkSugar 是具体装饰器。现在,我们可以创建一个咖啡对象,并动态地添加配料:

coffee = SimpleCoffee()
print(coffee.cost())

coffee_with_milk = Milk(coffee)
print(coffee_with_milk.cost()) 

coffee_with_milk_and_sugar = Sugar(coffee_with_milk)
print(coffee_with_milk_and_sugar.cost())

通过装饰器模式,我们可以在不修改原始 Coffee 类的情况下,动态地添加额外的功能,使得代码更加灵活和可维护。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值