类也是对象,所以类也有"类"

class Player:
    pass

print(type(Player))       # <class 'type'>

Player 这个类,它的"类"是 typetype 就是默认的元类

type 的另一面:动态创建类

type(name, bases, dict) 可以直接创建一个类

Player = type("Player", (), {"hp": 100})
p = Player()
print(p.hp)           # 100

等价于:

class Player:
    hp = 100

理解这点:class 关键字背后就是在调用 type()

自定义元类

继承 type,重写 __new____init__

class LowerNameMeta(type):
    def __new__(mcs, name, bases, attrs):
        # 把所有方法名改小写
        new_attrs = {
            (k.lower() if not k.startswith("_") else k): v
            for k, v in attrs.items()
        }
        return super().__new__(mcs, name, bases, new_attrs)


class Player(metaclass=LowerNameMeta):
    def Speak(self): print("hi")
    def WALK(self): print("walking")


p = Player()
p.speak()        # hi    (原来的 Speak 已被改成 speak)
p.walk()

用元类做"自动注册"

最常见的元类应用:类一被定义就自动注册到某个表

HANDLERS = {}

class HandlerMeta(type):
    def __init__(cls, name, bases, attrs):
        super().__init__(name, bases, attrs)
        if hasattr(cls, "command"):
            HANDLERS[cls.command] = cls


class Handler(metaclass=HandlerMeta):
    pass


class AddHandler(Handler):
    command = "add"
    def run(self, *args): print("adding", args)


class ListHandler(Handler):
    command = "list"
    def run(self, *args): print("listing")


# 不需要手动 register,定义类即注册
print(HANDLERS)
# {'add': <class 'AddHandler'>, 'list': <class 'ListHandler'>}

init_subclass:90% 场景的元类替代品

Python 3.6 引入的 __init_subclass__ 比元类简单多了:

HANDLERS = {}

class Handler:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if hasattr(cls, "command"):
            HANDLERS[cls.command] = cls


class AddHandler(Handler):
    command = "add"

效果完全一样,但不用写元类

何时真的需要元类

  • 写框架,要在 class 创建时改属性 / 注入方法 / 校验定义
  • ORM(SQLAlchemy)、ABC、Django Model 内部都用元类

写应用代码 99% 用不到元类。看到了能读懂就行。

"Magic 程度排行"

工具 复杂度 何时用
函数 / 装饰器 90%
类装饰器 想给类批量加属性
__init_subclass__ 想在子类创建时做事
描述符 想批量复用属性逻辑
元类 最高 写框架,无可替代时

下一篇讲类型注解 typing——给代码加类型说明。