Python 3 中 __metaclass__ 属性无效,须用 metaclass=MyMeta 关键字参数;元类的 __call__ 方法用于实例化前拦截,可校验参数、缓存实例等,但需注意资源泄漏、多重继承冲突及与 dataclass 等的兼容性。

Python 3 中没有 __metaclass__ 属性,直接写它不会生效 —— 这是绝大多数人踩坑的第一步。
为什么 __metaclass__ 在 Python 3 里不工作
Python 2 的类定义中可以通过在类体里声明 __metaclass__ = MyMeta 来指定元类,但 Python 3 彻底移除了这个语法。如果你照搬老教程写:
class MyClass:
__metaclass__ = MyMeta # ← 这行被完全忽略,MyClass 仍用 type 创建结果就是元类逻辑压根没触发,初始化前拦截自然失败。
真正起作用的是关键字参数 metaclass,且必须放在类定义的括号参数里(即继承列表之后):
立即学习“Python免费学习笔记(深入)”;
安全 Python 代码沙箱:在隔离的沙箱中执行任意 Python 代码,预装 requests、NumPy、pandas 库。返回 stdout、stderr。
class MyClass(metaclass=MyMeta): # ✅ 正确写法
pass如何用元类在实例化前拦截并干预
元类的 __call__ 方法会在每次调用类(即 MyClass())时触发,它控制着“从类到实例”的全过程 —— 这才是拦截初始化逻辑的正确位置。
常见需求包括:校验参数、缓存单例、跳过 __init__、甚至返回已有对象而非新建实例。
-
__call__接收的是类本身 + 实例化时传入的参数,不是实例 - 若想阻止默认创建流程,不要调用
super().__call__(...) - 若想定制实例化,通常要手动调用
cls.__new__(cls, ...)和obj.__init__(...),但注意__init__可能被多次调用,需加防护
示例:禁止无参创建
class NoEmptyInit(type):
def __call__(cls, *args, **kwargs):
if not args and not kwargs:
raise TypeError(f"{cls.__name__} requires at least one argument")
return super().__call__(*args, **kwargs)
<p>class Person(metaclass=NoEmptyInit):
def <strong>init</strong>(self, name):
self.name = name</p><h1>Person() → 抛出 TypeError</h1><h1>Person("Alice") → 正常容易被忽略的兼容性与副作用
元类不是装饰器,它影响的是类的创建阶段,而非实例行为。很多你以为“拦截了初始化”的逻辑,其实只是绕过了 __init__,但 __new__ 已执行、内存已分配 —— 这在资源敏感场景下可能造成泄漏。
- 如果在
__call__中返回非本类实例(比如缓存对象),Python 不会再调用该对象的__init__,但也不会报错 - 多重继承时,元类必须兼容(比如都继承自
type,且__call__协作良好),否则TypeError: metaclass conflict - 使用
dataclass或NamedTuple的类不能随意套元类,它们内部已绑定特定元类
最常被漏掉的一点:__call__ 是元类的方法,不是类的方法;所有对类的调用都会经过它,包括子类实例化 —— 如果没做 isinstance 判断或白名单控制,很容易误伤。

















