python中符号 @ 的讲解——基础到进阶


本文内都是我自己去了解@的过程,感觉比网上的一些教程更具体一点,也是为了我个人记录一下学习的内容。(这里我只记录了 @ 作为函数装饰器 decorator 的用法,其作为矩阵乘法符号的用法这里就不讲述了)

一、基础用法

对于当前的函数在外层进行嵌套,但是又不想修改当前的函数,因此使用了@,下面是一段很多教程中都有类似的代码。

# Example code
def my_decorator(func):
    def wrapper(*args,**kwargs):
        print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
        return func(*args,**kwargs)
    return wrapper

@my_decorator
def add(a,b):
    print(a+b)

add(1,2)

输出为:

Calling add with args=(1, 2) and kwargs={}
3

但是,看完上面这段代码,你可能还是不够了解@具体做了些什么,下面是更细致一点的讲解

二、进阶理解

1. @到底做了什么?

在代码编译过程中,当编译到函数前具有@decorator的函数时,会将该函数替换为decorator()的返回值注意这里是返回值,所以我们可以看到我们前面使用的装饰函数的返回值是一个函数,就是将被装饰函数组装成了一个新的函数

2. @在什么时候进行的编译?

下面是一段测试:

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args} and kwargs={kwargs}")
        return func(*args, **kwargs)
    print("Compiled!")
    return wrapper

@my_decorator
def add(a, b):
    print(a + b)

@my_decorator
def sub(a, b):
    print(a - b)

add(1, b=2)

请大家思考一下运行这段代码后console中的输出是什么?

运行代码,输出如下

Compiled!
Compiled!
Calling add with args=(1,) and kwargs={‘b’: 2}
3

错误的理解

在运行到被装饰的函数时,运行 my_decorator() 函数,这也是我最开始的理解,如果按照这种理解方式,那么上面这段代码的输出应该为:

Compiled!
Calling add with args=(1,) and kwargs={‘b’: 2}
3

因为代码中只调用了一次 add() 函数,按理说只应该输出一个 “Compiled!” ,所以可发现上面的这种理解是错误的

正确的理解

在函数定义时,装饰函数就被调用了。
其实这种对于装饰器的处理方式是可以很好理解的,因为经过装饰后的函数在后面每次调用时其实都变成了一个装饰过的函数,所以编译器会在浏览到函数定义后就将该函数的装饰函数编译然后替换掉被装饰的函数,这样就不需要每次都重新编译了。

三、包含类的装饰器

类作为装饰器

相信有些时候大家会看到 @ 后并不是函数,而是一个类,那么这时候编译器又是怎么处理的呢?
其实在这种情况下我们要求这个类一定要有 __call__() 方法,这样才可以作为装饰器,而这里 __call__() 函数就会替代被装饰的函数,示例如下:

class MyDecorator:
    def __init__(self, func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("Before calling the function")
        result = self.func(*args, **kwargs)
        print("After calling the function")
        print(result)
@MyDecorator
def my_function():
    print("Inside the function")
    return 1
my_function()

输出为:

Before calling the function
Inside the function
After calling the function
1

类被装饰

其实和前面的类似,也是在类定义的时候去调用装饰函数,要注意前后的定义一致,下面我也给出一个示例供大家理解参考:

def class_decorator(cls):
    # This will be printed when the class is defined, not when an instance is created
    print(f"Decorating class {cls.__name__}")
    # Add a new method to the class
    cls.new_method = lambda self: print("This is a new method added by the decorator")
    return cls

@class_decorator
class MyClass:
    def __init__(self, value):
        self.value = value

    def display(self):
        print(f"Value: {self.value}")

# Creating an instance of MyClass
instance = MyClass(10)
instance.display()
instance.new_method()  # Call the new method added by the decorator

输出为:

Decorating class MyClass
Value: 10
This is a new method added by the decorator

总结

相信看完上面的教程,大家对于 @ 一定有了更好的理解,下次再看到代码里每个函数前面都带一个 @ 也就不会发懵了。
(这也是我第一次发布教程,如有问题请联系我!)

作者:Huaisure

物联沃分享整理
物联沃-IOTWORD物联网 » python中符号 @ 的讲解——基础到进阶

发表回复