Loading... <div class="tip inlineBlock warning simple"> 🤖 本博客内容使用 GPT-4 技术进行润色 </div> Python 装饰器以其强大的功能和简洁的实现方式,为编程提供了极大的便利,使得函数行为的扩展变得触手可及。然而,在深入挖掘装饰器的奥秘之前,我们必须先建立对函数和闭包这两大核心概念的坚实理解。 ## 函数:Python 编程的基石 在 Python 的世界里,函数是构建程序的基本模块。它们透过 `def` 关键字声明,拥有独特的函数名,并可携带一系列可选参数。最终,函数通过 `return` 关键字来输出它们的计算结果。 ### 1、函数的定义与作用域 在 Python 中,变量的可访问性是由其定义的位置决定的,这就构成了变量的作用域。作用域定义了程序的哪些部分可以访问特定的变量名称。Python 的作用域分为四种类型: - L (Local)局部作用域:定义在函数内部的变量。 - E (Enclosing)外围作用域:位于嵌套函数的外部函数中的变量。 - G (Global)全局作用域:整个文件范围内的变量。 - B (Built-in)内建作用域:Python 语言自带的特殊变量。 ```python b_count = int(6.6) # 内建作用域 g_count = 8 # 全局作用域 def outer(): e_count = 6 # 外围作用域 def inner(): l_count = 3 # 局部作用域 ``` 为了深入理解,我们主要关注全局作用域和局部作用域,利用 Python 的 `globals()` 函数,可以获取一个包含当前全局作用域中所有变量的字典。 ### 2、变量的解析规则与生命周期 尽管函数内部可以访问外部定义的全局变量,但是一旦在函数内部创建了一个同名变量,它就会在当前的作用域内覆盖全局变量。变量的查找遵循 L -> E -> G -> B 的顺序,即从局部作用域开始,逐级向外层作用域查找,直至找到为止。 ```python def g_print(): # 局部作用域无该变量,输出全局变量 print(string) def l_print(): string = 'This is a dog!' # 局部作用域有该变量,输出局部变量 print(string) if __name__ == '__main__': string = 'This is a cat!' g_print() # 输出:This is a cat! l_print() # 输出:This is a dog! ``` 变量的生命周期受其所在命名空间的影响。一旦函数执行完毕,其内部的局部变量便会消失。 ```python def print_string(): string = 'This is a dog!' print(string) if __name__ == '__main__': print_string() # 此处尝试访问 string 将引发错误,因为它在这个作用域内不可见 print(string) # 抛出错误:NameError: name 'string' is not defined ``` ### 3、函数的参数 在 Python 中,函数的参数设计极具灵活性,它们在函数内部表现为局部变量,根据传递和定义方式的不同,可以分为以下几类: - **必备参数(Positional Arguments)**:这些参数是必不可少的,且需要按照函数定义时的顺序准确传递。在函数调用时,每一个必备参数都需要对应一个实际的参数值。 - **关键字参数(Keyword Arguments)**:调用函数时,关键字参数允许通过指定参数名来设置参数值,这意味着即使参数顺序改变,只要参数名正确,函数也能接收到正确的参数值。 - **默认参数(Default Arguments)**:在函数定义时,可以为参数设置默认值。如果在调用函数时没有传递这些参数,那么将使用其默认值。 - **不定长参数(Arbitrary Argument Lists)**:当你想要函数接收任意数量参数时,不定长参数就派上用场。在参数名前加上一个星号 `*args` 表示接收为元组的形式,而两个星号 `**kwargs` 表示接收为字典的形式,分别用于未命名和命名参数。 下面是一个展示这四种参数类型的 Python 函数示例: ```python # 定义一个函数,展示不同类型的参数 def introduce(name, greeting="Hello", *hobbies, **personal_info): print(f"{greeting}, my name is {name}.") # 打印不定长参数(元组) if hobbies: print("My hobbies are:", ", ".join(hobbies)) # 打印关键字不定长参数(字典) for key, value in personal_info.items(): print(f"My {key} is {value}.") # 调用函数 introduce( "Alice", # 必备参数 greeting="Hi", # 关键字参数 "reading", "traveling", # 不定长参数 age=29, city="New York" # 关键字不定长参数 ) ``` 在这个例子中,`name` 是必备参数,`greeting` 是带有默认值的参数,`*hobbies` 是不定长参数,而 `**personal_info` 是关键字不定长参数。当我们调用 `introduce` 函数时,我们按照这些规则提供了相应的参数。这种参数设计使得 Python 函数非常灵活,能够适应各种不同的调用情境。 ### 4、函数嵌套 Python 支持函数嵌套,即你可以在一个函数内部定义另一个函数。嵌套函数可以访问其外层作用域中的变量,这一点在闭包和装饰器的设计中尤为重要。 ```python def out_function(): string = 'This is a dog!' def in_function(): print(string) return in_function() if __name__ == '__main__': out_function() # 输出:This is a dog! ``` 在上述示例中,`in_function` 作为内嵌函数,能够访问其外层函数 `out_function` 中定义的变量 `string`。当 `out_function` 被调用,它会创建并返回 `in_function`,后者在调用时输出了 `string` 变量的值。这一过程展示了 Python 中作用域和变量生命周期的精妙互动。 ## 闭包:保持状态的函数 在 Python 的编程实践中,闭包(Closure)是一个函数,它能够捕获并保持对其词法作用域中变量的引用。这意味着即使函数在其定义环境之外被调用,它仍然能够访问那些变量。闭包的关键特性包括: * **环境捕捉**:闭包在被定义时捕获周围的状态,即使外层函数执行完毕,这些状态仍然可用。 * **封装性**:闭包封装了内部变量,防止外部直接访问,实现了数据的隐蔽性和安全性。 * **持久性**:闭包内的变量生命周期超出了它们的作用域,只要闭包还在使用,这些变量就会一直存活。 在 Python 中,闭包的存在可以通过函数对象的 `__closure__` 属性来确认,该属性包含了闭包中捕获的变量的细胞(cell)对象,每一个细胞内部存储了闭包中引用的自由变量的一个副本。这里所说的自由变量,是指那些在函数定义中被使用到,但既不是函数的参数也不是局部变量的变量。 为了更深入地理解闭包,我们可以来看一个具体的例子: ```python def make_multiplier(x): def multiplier(n): return x * n return multiplier # 创建一个闭包实例,记住 x=3 的环境 times3 = make_multiplier(3) # 创建另一个闭包实例,记住 x=5 的环境 times5 = make_multiplier(5) # 利用闭包实例进行计算 print(times3(10)) # 输出结果为 30,因为 3 * 10 = 30 print(times5(10)) # 输出结果为 50,因为 5 * 10 = 50 ``` 在上述例子中,`make_multiplier` 函数返回了一个内嵌的 `multiplier` 函数,而这个内嵌函数闭包了外部函数的参数 `x`。即使 `make_multiplier` 函数的执行已经完成,闭包中的 `x` 仍然被 `multiplier` 函数保留和访问。 闭包不仅是 Python 函数式编程的基础,而且是装饰器的核心机制。装饰器使用闭包来扩展和修改函数的行为,它们使得在不修改原始函数的情况下增加功能变得可能,进而提升代码的可复用性和模块化。 ## 装饰器:增强函数功能 在 Python 中,函数装饰器是一种使用闭包概念实现的强大工具,它允许我们在不修改函数内部代码的前提下,增加额外的功能。装饰器本质上是一个接收函数作为参数并返回一个新函数的闭包。后面内容中,我们将一步步深入了解函数装饰器的工作原理和用法。 ### 1、Python 函数装饰器 装饰器是一种特殊的闭包,它接受一个函数作为参数,并返回一个功能增强的函数。看看下面的例子: ```python def out_function(function): def in_function(): string = function() print('string: ', string) # 注意应该返回函数本身,而不是函数的调用结果 return in_function def function(): string = 'This is a dog!' return string if __name__ == '__main__': decorated_function = out_function(function) decorated_function() # 输出:string: This is a dog! ``` ### 2、语法糖:装饰器的简洁方式 Python 允许使用 `@` 符号作为装饰器的语法糖,使得装饰器的应用更加简单。我们只需在函数定义之前加上 `@` 和装饰器的名称即可。例如: ```python @out_function def function(): string = 'This is a dog!' return string if __name__ == '__main__': # 装饰器输出:string: This is a dog! print(function()) # 输出返回值:None ``` 注意,虽然我们添加了装饰器,但是 `function()` 函数似乎没有返回值。这是因为我们在装饰器内部没有将函数的返回值通过 `return` 语句传递出来。 ### 3、适配不同参数的装饰器:`*args` 和 `**kwargs` 为了让装饰器能够处理带有不同参数的函数,我们需要使用 `*args` 和 `**kwargs` 这两个不定长参数,它们可以让装饰器接收任意数量和类型的参数。 ```python import time def Time(func): def wrapper(*args, **kwargs): t1 = time.time() result = func(*args, **kwargs) t2 = time.time() print("run time: {:.4f}s".format(t2 - t1)) return result return wrapper @Time def count_number(max_number, tag): count = 0 for item in range(max_number): if item % tag == 0: count += item return count if __name__ == '__main__': # 装饰器输出:run time: 0.4188s print(count_number(6666666, 2)) # 输出返回值:11111105555556 ``` 现在装饰器 `Time` 已经可以适配任意参数的函数了,`*args` 表示任何多个无名参数,它是一个元组;`**kwargs` 表示关键字参数,它是一个字典。在使用时,`*args` 必须位于 `**kwargs` 之前。最后,我们通过一个综合示例来演示如何使用这两个参数: ``` def noName(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) print(f'*args: {args}') print(f'**kwargs: {kwargs}') return result return wrapper @noName def function(a, b, c, num, string): return f'{a} {b} {c} {num} {string}' if __name__ == '__main__': print(function(1, 2, 3, num=123, string='function')) """ 输出结果: *args: (1, 2, 3) **kwargs: {'num': 123, 'string': 'function'} 1 2 3 123 function """ ``` 正如我们所见,Python 装饰器不仅是一个优雅的编程工具,它们还为代码的重构和模块化提供了巨大的便利。通过装饰器,我们可以无缝地增加函数功能,而不必更改函数本身的代码。这种能力在维护大型代码库时显得尤为宝贵,因为它允许我们扩展功能而不会引入潜在的新错误。 Last modification:March 1, 2024 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 如果觉得我的文章对你有用,请随意赞赏