九、闭包和装饰器

目录

  • 一、闭包
    • 1.1 内部函数的查询变量的顺序
    • 1.2 如何修改外部函数的同名变量
  • 二、装饰器
    • 2.1 带参数和返回值的装饰器
    • 2.3 装饰器带参数
    • 2.4 类的装饰器
    • 2.5 装饰器实现路由功能

一、闭包

闭包(Closure)是指函数对象(函数)与其周围的环境(包括引用了外部变量的函数及其引用的变量)的组合。闭包可以访问并修改其词法作用域中的变量,即使在函数调用结束后,这些变量仍然可以被访问和修改。
在 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 是内部函数,它定义在外部函数内部,并引用了外部函数的参数 x。
当调用 outer_function(5) 时,它返回了一个闭包 closure。closure 实际上是一个函数对象,可以调用并传入参数给 inner_function。
在上面的代码中,closure(3) 的结果是 8。即使 outer_function 调用已经结束,我们仍然可以访问和使用 inner_function 中引用的 x 变量。
闭包适用于需要在函数之间共享状态或记住特定上下文的情况。通过使用闭包,我们可以创建具有持久状态的函数对象,这对于某些编程问题是非常有用的。

注意点:
由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存。

1.1 内部函数的查询变量的顺序

Python 在嵌套函数内部查找变量时会按照以下顺序进行搜索:

  • 查找本地作用域(内部函数内部的变量)
  • 查找嵌套作用域(外部函数内部的变量)
  • 查找全局作用域(模块级别的变量)
  • 查找内置作用域(Python 内置的函数和模块)
    因此, 当外部函数和内部函数存在同名的变量时, 如果不特殊处理,那么内部函数只会优先处理自己作用域的同名变量.
def outer_function():
    x = 10

    def inner_function():
        x = 20
        print("Inner function:", x)

    inner_function()
    print("Outer function:", x)

outer_function()

在这个示例中,我们在内部函数 inner_function 中对变量 x 进行了赋值。
运行上面的代码将会得到以下输出:

Inner function: 20
Outer function: 10

我们可以看到,尽管在内部函数中修改了变量 x 的值,但外部函数中的变量 x 的值保持不变。这是因为在内部函数中使用了赋值操作,Python 将变量 x 视为一个新的本地变量,而不是对外部函数中的变量进行修改。

1.2 如何修改外部函数的同名变量

在 Python 中,当我们在一个嵌套函数内部定义一个同名的变量时,默认情况下,Python 会将这个变量视为一个新的本地变量,而不是引用外部的变量。这意味着,如果我们在嵌套函数中对这个变量进行修改,实际上只是修改了这个新的本地变量,而不是外部的变量。
使用 nonlocal 关键字可以改变这个默认行为,让嵌套函数能够访问并修改外部函数或者模块中的变量。
下面是一个示例来说明 nonlocal 的用法:

def outer_function():
    x = 10

    def inner_function():
        # 关键代码
        nonlocal x
        x += 1
        print("Inner function:", x)

    inner_function()
    print("Outer function:", x)

outer_function()

在这个示例中,inner_function 使用了 nonlocal 关键字来明确告诉 Python x 是来自于外部函数 outer_function 的变量。这样,在内部函数中对 x 的修改实际上是对外部函数中的 x 进行的修改。
运行上面的代码将会得到以下输出:

Inner function: 11
Outer function: 11

我们可以看到,内部函数成功地修改了外部函数的变量 x,并正确地打印出了增加后的值。
通过使用 nonlocal 关键字,我们能够更加灵活地访问和修改嵌套作用域中的变量,从而实现更加复杂的程序逻辑。

二、装饰器

装饰器(Decorator)是 Python 中一种特殊的语法和语法糖,用于修改函数的行为或扩展函数的功能。它们允许我们在不修改原始函数定义的情况下,通过将其包装在其他函数中来添加额外的逻辑。
装饰器的语法如下:

@decorator
def function():
    # 函数体
装饰器通过在函数定义前使用 @decorator 的语法糖来应用于特定的函数。这相当于将原始函数作为参数传递给装饰器函数,并将装饰器函数的返回值赋值给原始函数。这样,当我们调用原始函数时,其实是调用了被装饰后的函数。
2.1 不带参数和返回值的装饰器
下面是一个简单的示例来说明装饰器的用法:
def decorator_function(func):
    def wrapper():
        print("Before function execution")
        func()
        print("After function execution")
    
    return wrapper

@decorator_function
def hello():
    print("Hello, world!")

hello()

在这个示例中,我们定义了一个装饰器函数 decorator_function,它接受一个函数作为参数,并返回一个内部函数 wrapper。在 wrapper 函数内部,我们可以在调用原始函数之前和之后执行额外的逻辑。
通过在 hello 函数定义前使用 @decorator_function,我们将装饰器应用于 hello 函数。这样,当我们调用 hello 函数时,实际上会调用被装饰后的函数。
运行上面的代码将会得到以下输出:

Before function execution
Hello, world!
After function execution

我们可以看到,在调用 hello 函数之前和之后,装饰器函数 decorator_function 中定义的逻辑被执行了。
装饰器在实际开发中有很多应用场景,比如日志记录、性能分析、输入验证等。它们提供了一种简单而灵活的方式来修改函数的行为,同时保持代码的可读性和可维护性。

2.1 带参数和返回值的装饰器

上面是无参数的装饰器使用, 带参数和不定参数的也可以使用.
如果要应用装饰器到有参数的函数上,我们需要使用一个更通用的装饰器模式。在这种情况下,我们需要定义一个装饰器函数,它接受任意个数和类型的参数,并返回一个内部函数,该内部函数代替原始函数的执行。
下面是一个示例来说明如何应用装饰器到有参数的函数上:

def decorator_function(func):
    def wrapper(*args, **kwargs):
        print("Before function execution")
        result = func(*args, **kwargs)
        print("After function execution")
        return result
    
    return wrapper

@decorator_function
def add(x, y):
    return x + y

result = add(3, 4)
print("Result:", result)

在这个示例中,我们定义了一个通用的装饰器函数 decorator_function,它接受一个函数作为参数,并返回一个内部函数 wrapper。wrapper 函数使用 *args 和 **kwargs 来接受任意个数和类型的参数。
在 wrapper 函数内部,我们可以在调用原始函数之前和之后执行额外的逻辑。并且,我们使用 func(*args, **kwargs) 来调用原始函数,并将其参数传递给原始函数。
通过 @decorator_function 将装饰器应用于 add 函数,我们可以在调用 add 函数前后执行装饰器函数中的逻辑。
运行上面的代码将会得到以下输出:

Before function execution
After function execution
Result: 7

2.3 装饰器带参数

装饰器可以接受参数。这样的装饰器被称为带参数的装饰器。如果我们需要在装饰器中使用参数,那么我们需要在装饰器函数的外层定义一个接受参数的函数,然后在内层定义真正的装饰器函数。
这样做的原因是,通过这种多层的嵌套结构,我们可以在外层函数中接受装饰器的参数,并将这些参数传递给内层的装饰器函数。内层装饰器函数则负责接受被装饰的函数作为参数,并返回一个新的函数或类来替代原始的函数或类。
这样的设计使得装饰器可以非常灵活地处理不同的场景和需求,因为装饰器函数可以根据传递进来的参数来自定义装饰器的行为。
下面是一个示例展示如何定义带参数的装饰器:

# 定义外部函数接收参数
def repeat(n):
    # 定义装饰器函数
    def decorator(func):
        # 定义内部函数用来代替原始函数
        def wrapper(*args, **kwargs):
            for _ in range(n):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

2.4 类的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。

class Test():
    def __call__(self):
        print('call me!')

t = Test()
t()  # call me
类的装饰器使用和普通函数的装饰器使用方式是一样的, 只是@类名的方式代替@函数名的方式, 然后会在__init__方法接受参数
class Test(object):
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s"%func.__name__)
        self.__func = func
    def __call__(self):
        print("---装饰器中的功能---")
        self.__func()
        
@Test
def hello():
    print("----test---")
    
hello()

输出结果:

---初始化---
func name is hello
---装饰器中的功能---
----test---

同样的如果要对待参数和返回值的函数使用类装饰器的话, 只需要修改下类的__call__方法,将参数添加上即可, 例如:

class Test(object):
    def __init__(self, func):
        print("---初始化---")
        print("func name is %s" % func.__name__)
        # 存储到对象中
        self.__func = func

    def __call__(self, *args, **kwargs):
        print("---装饰器中的功能---")
        # 调用目标函数,并获取返回值
        result = self.__func(*args, **kwargs)
        # 在功能后输出结果
        print("Function result:", result)
        return result


@Test
def hello(name):
    print("Hello, %s!" % name)
    return "Function execution complete"


result = hello("Alice")
print(result)

输出结果:

---初始化---
func name is hello
---装饰器中的功能---
Hello, Alice!
Function result: Function execution complete
Function execution complete

如果还想在接收装饰器的参数,那么可以通过在 Test 类中定义 init 方法来实现。

class Test(object):
    def __init__(self, arg1, arg2):
        # 接收装饰器参数
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):
        # 定义内部函数用来代替原始函数
        def wrapper(*args, **kwargs):
            print("---装饰器中的参数---")
            print("arg1:", self.arg1)
            print("arg2:", self.arg2)
            print("---装饰器中的功能---")
            # 调用目标函数
            result = func(*args, **kwargs)
            return result

        # 返回装饰后的函数
        return wrapper


@Test(arg1="value1", arg2="value2")
def hello(name):
    print("Hello, %s!" % name)
    return "Function execution complete"


result = hello("Alice")
print(result)

输出结果:

---装饰器中的参数---
arg1: value1
arg2: value2
---装饰器中的功能---
Hello, Alice!
Function execution complete

2.5 装饰器实现路由功能

在 web 开发中,路由是指将 URL 请求映射到相应的处理函数或视图函数的过程。通过使用装饰器,我们可以实现根据不同的 URL 调用不同的处理函数或视图函数。
下面是一个简单的示例来说明如何使用装饰器实现路由功能:

# 定义一个字典,用于存储路由信息
routes = {}


# 定义外部函数接收路由参数
def route(path):
    # 定义装饰器函数
    def decorator(func):
        routes[path] = func

        # 定义接收函数参数的内部函数
        def func2(file_name):
            return func(file_name)

        return func2

    return decorator


# 定义路由处理函数
@route('/')
def index(file_name):
    return "index.html"


@route('/about')
def about(file_name):
    return f"{file_name}.html"


# 定义路由处理函数
def handle_route(path):
    if path in routes:
        return routes[path](path)
    else:
        return '404 Not Found'


# 使用路由处理函数处理请求
print(handle_route('/'))  # 输出:index.html
print(handle_route('/about'))  # 输出:/about.html
print(handle_route('/contact'))  # 输出:404 Not Found

在这个示例中,route 是一个装饰器工厂函数,它接受路由路径作为参数。decorator 是真正的装饰器,它接受路由处理函数作为参数,并将路由路径和处理函数注册到 routes 字典中。而被装饰的函数被返回并保持不变。
通过使用 @route 语法将装饰器应用到函数上,可以将函数和对应的路由路径关联起来。然后,可以通过调用 handle_route 函数来根据路由路径执行相应的处理函数。如果路径在 routes 字典中,则执行对应的处理函数;否则返回 “404 Not Found”。

你可能感兴趣的:(python,网络,前端,linux)