Python
@在Python中的作用总结
发布于 2026年8月29日
Python 中,@ 在语言层面主要有两大用途:
- 装饰器语法:
@decorator - 矩阵乘法运算符:
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)
分为三步:
repeat(3)根据参数创建装饰器- 装饰器接收
hello - 装饰器返回包装后的函数
需要区分:
@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
子类没有实现抽象方法时,不能实例化。
与 classmethod、staticmethod、property 一起使用时,一般让 @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 语言特性。
五、常见误区
@decorator不是注释或标签,而是实际会执行的表达式。- 装饰器通常在函数定义时应用,不是在函数第一次调用时应用。
- 装饰器不一定返回函数,也可以返回类、实例甚至普通值。
- 多个装饰器按从下到上的顺序包装。
- 带括号的
@decorator(...)通常先调用装饰器工厂。 - 包装函数时应优先使用
functools.wraps。 - 异步函数的包装函数通常也应使用
async def和await。 @是矩阵乘法,不是普通乘法;具体计算规则由操作数类型决定。@=不保证一定修改原对象。- 自定义
__matmul__遇到不支持的类型时应返回NotImplemented。
一句话概括:
@ 位于定义之前:装饰函数或类
@ 位于两个表达式之间:矩阵乘法
@=:矩阵乘法复合赋值