浏览知识库目录

Python

@在Python中的作用总结

Python 中,@ 在语言层面主要有两大用途:

  1. 装饰器语法:@decorator
  2. 矩阵乘法运算符:a @ b,以及复合赋值 a @= b

此外,部分库和工具会在自己的配置语法中使用 @,但那不属于 Python 语法。

一、装饰器 @decorator

装饰器用于在不直接修改原函数或类定义的情况下,对它进行包装、注册、标记或增强。

1. 最基本的函数装饰器

def log_call(func):
    def wrapper():
        print("函数开始执行")
        result = func()
        print("函数执行结束")
        return result

    return wrapper

@log_call
def hello():
    print("Hello")

hello()

这里:

@log_call
def hello():
    ...

基本等价于:

def hello():
    ...

hello = log_call(hello)

装饰器本质上接收一个函数,并返回一个新的可调用对象。

2. 保留函数元信息:functools.wraps

直接返回包装函数会丢失原函数的名字、文档字符串和注解等信息:

print(hello.__name__)  # wrapper

标准写法是使用 functools.wraps

from functools import wraps


def log_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用函数:{func.__name__}")
        return func(*args, **kwargs)

    return wrapper

@wraps(func) 会复制或维护:

  • __name__
  • __qualname__
  • __doc__
  • __annotations__
  • __wrapped__

其中 __wrapped__ 对调试、类型检查和 inspect 很重要。

3. 支持任意参数的装饰器

通常使用:

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper

例如:

from functools import wraps


def log_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("位置参数:", args)
        print("关键字参数:", kwargs)
        return func(*args, **kwargs)

    return wrapper


@log_call
def add(a, b=0):
    return a + b


print(add(10, b=20))

4. 带参数的装饰器

例如希望写成:

@repeat(3)
def hello():
    print("Hello")

需要使用三层函数:

from functools import wraps


def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None

            for _ in range(times):
                result = func(*args, **kwargs)

            return result

        return wrapper

    return decorator

其执行关系是:

repeat(3)(hello)

分为三步:

  1. repeat(3) 根据参数创建装饰器
  2. 装饰器接收 hello
  3. 装饰器返回包装后的函数

需要区分:

@decorator
def f():
    ...

和:

@decorator()
def f():
    ...

前者将 f 直接交给 decorator

f = decorator(f)

后者先调用 decorator(),再把 f 交给返回值:

f = decorator()(f)

5. 多个装饰器叠加

@decorator_a
@decorator_b
def func():
    ...

应用效果等价于:

func = decorator_a(decorator_b(func))

也就是最靠近函数的装饰器先包装函数。

例如:

def decorator_a(func):
    def wrapper():
        print("A 开始")
        func()
        print("A 结束")

    return wrapper


def decorator_b(func):
    def wrapper():
        print("B 开始")
        func()
        print("B 结束")

    return wrapper


@decorator_a
@decorator_b
def hello():
    print("Hello")


hello()

输出:

A 开始
B 开始
Hello
B 结束
A 结束

有一个容易混淆的细节:

  • 装饰器表达式在定义函数时从上到下求值
  • 装饰器包装函数时从下到上应用

6. 装饰器在什么时候执行

装饰器不是等到调用函数时才创建,而是在执行函数定义语句时应用。

def register(func):
    print("注册:", func.__name__)
    return func


@register
def hello():
    print("Hello")

即使没有调用 hello(),执行到函数定义时也会打印:

注册: hello

因此装饰器经常用来做注册:

routes = {}


def route(path):
    def decorator(func):
        routes[path] = func
        return func

    return decorator


@route("/users")
def get_users():
    return ["Alice", "Bob"]

很多 Web 框架的路由语法就是这个原理。

7. 类作为装饰器

只要对象可调用,就可以当作装饰器。

类实例装饰函数

from functools import update_wrapper


class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0
        update_wrapper(self, func)

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"第 {self.count} 次调用")
        return self.func(*args, **kwargs)


@Counter
def hello():
    print("Hello")


hello()
hello()

这里:

@Counter
def hello():
    ...

等价于:

hello = Counter(hello)

此后 hello 已经是一个 Counter 实例。

带参数的类装饰器

class Repeat:
    def __init__(self, times):
        self.times = times

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            result = None

            for _ in range(self.times):
                result = func(*args, **kwargs)

            return result

        return wrapper


@Repeat(3)
def hello():
    print("Hello")

8. 装饰类

装饰器不仅能修饰函数,也能修饰类:

def add_repr(cls):
    def __repr__(self):
        return f"{cls.__name__}({self.__dict__!r})"

    cls.__repr__ = __repr__
    return cls


@add_repr
class User:
    def __init__(self, name):
        self.name = name


print(User("Alice"))

大致等价于:

class User:
    ...

User = add_repr(User)

类装饰器可以:

  • 修改类
  • 向类添加属性或方法
  • 注册类
  • 返回另一个类
  • 将类替换成其他对象

9. 常用内置装饰器

@staticmethod

定义不自动接收实例或类的静态方法:

class Math:
    @staticmethod
    def add(a, b):
        return a + b


print(Math.add(1, 2))

它本质上更像是放在类命名空间里的普通函数。

@classmethod

类方法自动接收类对象 cls

class User:
    def __init__(self, name):
        self.name = name

    @classmethod
    def anonymous(cls):
        return cls("匿名用户")


user = User.anonymous()

常用于:

  • 备用构造函数
  • 根据类状态创建实例
  • 支持继承的工厂方法

@property

将方法变成只读属性:

class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius ** 2


circle = Circle(2)
print(circle.area)  # 不需要写 circle.area()

@属性名.setter

定义属性赋值逻辑:

class User:
    def __init__(self, age):
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("年龄不能为负数")

        self._age = value

还可以定义删除逻辑:

@age.deleter
def age(self):
    del self._age

10. 标准库中的常用装饰器

@dataclass

自动生成初始化、比较、表示等方法:

from dataclasses import dataclass


@dataclass
class User:
    name: str
    age: int

通常会自动生成 __init____repr____eq__ 等方法。

@functools.lru_cache

缓存函数结果:

from functools import lru_cache


@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n

    return fibonacci(n - 1) + fibonacci(n - 2)

适合参数可哈希且相同输入总是产生相同结果的函数。

@functools.cache

无大小限制的缓存:

from functools import cache


@cache
def fibonacci(n):
    ...

@functools.cached_property

第一次访问时计算,之后使用缓存:

from functools import cached_property


class Data:
    @cached_property
    def expensive_result(self):
        print("正在计算")
        return sum(range(1_000_000))

@functools.total_ordering

根据少量比较方法补全其他比较操作:

from functools import total_ordering


@total_ordering
class Version:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return self.value == other.value

    def __lt__(self, other):
        return self.value < other.value

@contextlib.contextmanager

用生成器创建上下文管理器:

from contextlib import contextmanager


@contextmanager
def managed_resource():
    print("获取资源")

    try:
        yield "resource"
    finally:
        print("释放资源")


with managed_resource() as resource:
    print(resource)

@abc.abstractmethod

定义抽象方法:

from abc import ABC, abstractmethod


class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

子类没有实现抽象方法时,不能实例化。

classmethodstaticmethodproperty 一起使用时,一般让 @abstractmethod 更靠近函数:

class Factory(ABC):
    @classmethod
    @abstractmethod
    def create(cls):
        pass

因为装饰器顺序会影响最终行为。

11. 类型检查相关装饰器

这些装饰器很多主要服务于静态类型检查器。

@typing.overload

描述同一个函数的多个类型签名:

from typing import overload


@overload
def convert(value: int) -> str:
    ...


@overload
def convert(value: str) -> int:
    ...


def convert(value):
    if isinstance(value, int):
        return str(value)

    return int(value)

注意:@overload 声明之后仍然需要一个真正的实现。

@typing.final

表示类不应被继承,或者方法不应被重写:

from typing import final


@final
class Config:
    pass

它主要是给类型检查器看的,通常不会在运行时阻止继承。

@typing.override

明确表示方法是在重写父类方法:

from typing import override


class Animal:
    def speak(self) -> str:
        return ""


class Dog(Animal):
    @override
    def speak(self) -> str:
        return "汪"

这能帮助类型检查器发现方法名拼错或签名不匹配。typing.override 在 Python 3.12 加入;旧版本通常使用 typing_extensions.override

@typing.runtime_checkable

Protocol 支持有限的运行时检查:

from typing import Protocol, runtime_checkable


@runtime_checkable
class Closable(Protocol):
    def close(self) -> None:
        ...

之后可以使用:

isinstance(obj, Closable)

不过运行时主要检查成员是否存在,不会完整验证类型签名。

12. 装饰实例方法时的注意点

装饰实例方法时,包装函数的第一个参数通常是 self

from functools import wraps


def log_method(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        print(f"对象:{self!r}")
        return func(self, *args, **kwargs)

    return wrapper


class User:
    @log_method
    def say(self, message):
        print(message)

也可以统一写成 *args, **kwargs,此时 args[0] 通常就是 self

13. 异步函数装饰器

装饰 async def 时,包装函数通常也应该是异步函数:

from functools import wraps


def log_async(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        print("异步函数开始")
        result = await func(*args, **kwargs)
        print("异步函数结束")
        return result

    return wrapper

如果忘记 await,可能返回协程对象而不是实际结果。

14. 装饰器可能改变对象类型

装饰器不一定返回函数:

def replace_with_number(func):
    return 100


@replace_with_number
def hello():
    pass


print(hello)  # 100

所以装饰器的准确含义是:

接收被定义的函数或类,并用返回值重新绑定原名称。

只是实践中通常返回包装函数或原对象。

15. 装饰器表达式

现代 Python 允许较灵活的装饰器表达式,例如:

decorators = [staticmethod]


class Example:
    @decorators[0]
    def hello():
        print("Hello")

不过实际项目中最好保持装饰器表达式简单,避免影响可读性。

二、矩阵乘法运算符 @

Python 3.5 引入了 @,主要服务于 NumPy 等数值计算场景。

普通数字一般不支持:

1 @ 2

会抛出 TypeError,因为整数没有定义矩阵乘法。

1. NumPy 中的矩阵乘法

import numpy as np


a = np.array([
    [1, 2],
    [3, 4],
])

b = np.array([
    [5, 6],
    [7, 8],
])

result = a @ b
print(result)

结果:

[[19 22]
 [43 50]]

计算过程是:

19 = 1×5 + 2×7
22 = 1×6 + 2×8
43 = 3×5 + 4×7
50 = 3×6 + 4×8

需要区分:

a * b   # 对应位置逐元素相乘
a @ b   # 矩阵乘法

2. @ 对应的特殊方法

可以在自定义类中实现:

class Matrix:
    def __init__(self, value):
        self.value = value

    def __matmul__(self, other):
        return Matrix(self.value * other.value)

    def __repr__(self):
        return f"Matrix({self.value})"


a = Matrix(10)
b = Matrix(20)

print(a @ b)  # Matrix(200)

表达式:

a @ b

主要对应:

a.__matmul__(b)

涉及三个特殊方法:

语法 特殊方法 含义
a @ b a.__matmul__(b) 正向矩阵乘法
a @ b b.__rmatmul__(a) 反向矩阵乘法
a @= b a.__imatmul__(b) 原地矩阵乘法

3. 反向矩阵乘法 __rmatmul__

如果左操作数不支持相应运算,Python 可以尝试右操作数:

class RightMatrix:
    def __rmatmul__(self, other):
        return f"{other!r} 与 RightMatrix 相乘"


obj = RightMatrix()
print(10 @ obj)

简单理解,Python会尝试:

left.__matmul__(right)

如果不支持,可能继续尝试:

right.__rmatmul__(left)

自定义运算符时,不能处理某种类型最好返回 NotImplemented

def __matmul__(self, other):
    if not isinstance(other, Matrix):
        return NotImplemented

    ...

不要轻易写成:

return None

因为 None 表示运算成功且结果就是 None,而 NotImplemented 会让 Python 尝试其他分派方案。

4. 复合赋值 @=

a @= b

优先尝试:

a.__imatmul__(b)

例如:

class Matrix:
    def __init__(self, value):
        self.value = value

    def __imatmul__(self, other):
        self.value *= other.value
        return self

这里可以直接修改原对象。

如果没有合适的 __imatmul__,Python通常会回退到类似:

a = a @ b

因此 @= 不一定原地修改对象,具体取决于类型的实现。

5. @ 的优先级

@ 和以下运算符处于同一优先级层级:

*
/
//
%

它们一般按从左到右结合:

a @ b * c

相当于:

(a @ b) * c

而:

a + b @ c

相当于:

a + (b @ c)

复杂表达式中建议主动使用括号。

6. 批量矩阵乘法

在 NumPy 等库中,@ 不一定只处理二维矩阵,还可以处理向量和高维数组。

例如:

import numpy as np


vector_a = np.array([1, 2, 3])
vector_b = np.array([4, 5, 6])

print(vector_a @ vector_b)  # 32

这里相当于向量点积:

1×4 + 2×5 + 3×6 = 32

具体的维度提升、广播和结果形状由相应库定义,不是 Python 语法本身规定的。

三、@ 在第三方库中的常见应用

这些本质上仍然是装饰器。

Web 路由

@app.get("/users")
def get_users():
    ...

用于注册 HTTP 路由。

测试框架

@pytest.mark.parametrize(
    "a,b,result",
    [
        (1, 2, 3),
        (2, 3, 5),
    ],
)
def test_add(a, b, result):
    assert a + b == result

用于标记测试或提供参数。

ORM

@validates("email")
def validate_email(self, key, value):
    ...

用于注册字段验证器等行为。

命令行框架

@click.command()
@click.option("--name")
def hello(name):
    ...

用于注册命令和声明参数。

这些看起来用途不同,但原理始终是:

原名称 = 装饰器(原对象)

四、不是 Python 语言语法的 @

有些地方会看到 @,但它们属于其他工具。

pip 依赖中的直接引用

package-name @ https://example.com/package.whl

这属于 Python 包依赖格式,不是 .py 文件中的普通 Python 表达式。

邮箱地址

email = "user@example.com"

这里的 @ 只是字符串内容。

IPython 或框架自己的语法

某些交互环境、模板、配置文件可能赋予 @ 特殊意义,需要按对应工具的规则解释,不能当作 Python 语言特性。

五、常见误区

  1. @decorator 不是注释或标签,而是实际会执行的表达式。
  2. 装饰器通常在函数定义时应用,不是在函数第一次调用时应用。
  3. 装饰器不一定返回函数,也可以返回类、实例甚至普通值。
  4. 多个装饰器按从下到上的顺序包装。
  5. 带括号的 @decorator(...) 通常先调用装饰器工厂。
  6. 包装函数时应优先使用 functools.wraps
  7. 异步函数的包装函数通常也应使用 async defawait
  8. @ 是矩阵乘法,不是普通乘法;具体计算规则由操作数类型决定。
  9. @= 不保证一定修改原对象。
  10. 自定义 __matmul__ 遇到不支持的类型时应返回 NotImplemented

一句话概括:

@ 位于定义之前:装饰函数或类
@ 位于两个表达式之间:矩阵乘法
@=:矩阵乘法复合赋值