,

python 元类

类的创建过程 使用 class 关键字定义类时,Python 底层会调用元类来创建类: 也就是说,class …

类的创建过程

使用 class 关键字定义类时,Python 底层会调用元类来创建类:

class Dog:
   pass


# 等效于
class Dog(metaclass=type):
   pass


# 等效于
Dog = type("Dog", (), {})

# 等效于
Dog = type.__call__(type, "Dog", (), {})

也就是说,class 关键字只是语法糖,底层仍然是通过 type() 创建类。

自定义元类

通过继承 type,可以自定义元类,控制类的创建过程。

class MyMeta(type):
   def __new__(mcs, name, bases, namespace):
       print(f"正在创建类: {name}")
       print(f"父类: {bases}")
       print(f"属性: {list(namespace.keys())}")

       # 必须调用 type.__new__ 来真正创建类
       cls = super().__new__(mcs, name, bases, namespace)
       return cls


# 使用 metaclass 参数指定元类
class Dog(metaclass=MyMeta):
   species = "Canis familiaris"

   def bark(self):
       print("Woof!")


# 等效于
# def bark(self):
#     print("Woof!")


# Dog = type.__call__(MyMeta, "Dog", (), {"species": "Canis familiaris", "bark": bark})

# 输出:
# 正在创建类: Dog
# 父类: ()
# 属性: ['__module__', '__qualname__', 'species', 'bark']

参数说明:

  • mcs:元类自身(类似类方法中的 cls
  • name:类名字符串
  • bases:父类元组
  • namespace:类属性的字典

深入:元类的查找顺序

子类会继承父类的元类:

class MyMeta(type):
   pass

class Base(metaclass=MyMeta):
   pass

class Child(Base):  # 自动继承 MyMeta
   pass

print(type(Child))  # <class '__main__.MyMeta'>

如果父类元类不兼容,需要使用更通用的元类:

class MetaA(type):
   pass

class MetaB(type):
   pass

class A(metaclass=MetaA):
   pass

class B(metaclass=MetaB):
   pass

# class C(A, B): pass # TypeError! 元类冲突

# 解决方法:创建兼容的元类
class CommonMeta(MetaA, MetaB):
   pass

class C(A, B, metaclass=CommonMeta):  # 正常
   pass

总结

概念说明
元类创建类的类,默认是 type
__new__创建类,返回类对象
__init__初始化类,无返回值
__call__控制类的实例化过程
应用场景命名检查、自动注册、方法增强、ORM 等

一、实现单例元类

编写一个元类 SingletonMeta,使得任何使用该元类的类都自动成为单例模式:

class SingletonMeta(type):
    _instance = {}

    def __call__(cls, *args, **kwds):
        if cls not in cls._instance:
            ins = super().__call__(*args, **kwds)
            cls._instance[cls] = ins
        return cls._instance[cls]

    pass


class Database(metaclass=SingletonMeta):
    def __init__(self, host):
        self.host = host


db1 = Database("localhost")
db2 = Database("remote")
print(db1 is db2)  # 应该输出 True
print(db1.host)  # 应该输出 localhost


class Player(metaclass=SingletonMeta):
    pass


p1 = Player()
p2 = Player()

print(p1 is p2)

二、自动注册子类

编写一个元类 PluginMeta,使得任何继承自 Plugin 的子类都会被自动注册到 PluginMeta.registry 字典中(键为类名,值为类本身):

class PluginMeta(type):
    registry = {}

    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        if name != "Plugin":
            mcs.registry[name] = cls
        return cls

    pass


class Plugin(metaclass=PluginMeta):
    pass


class ImagePlugin(Plugin):
    pass


class TextPlugin(Plugin):
    pass


class VideoPlugin(Plugin):
    pass


print(PluginMeta.registry)
# 应该输出类似:{'ImagePlugin': <class '__main__.ImagePlugin'>, 'TextPlugin': <class '__main__.TextPlugin'>}

三、为所有方法添加日志

编写一个元类 LogMeta,自动为类中每个非私有方法(即不以 _ 开头的方法)添加执行日志。调用方法时,先打印 [LOG] 调用 {方法名},再执行原方法:

class LogMeta(type):
    def __new__(mcs, name, bases, namespace):
        for key, value in namespace.items():
            if not key.startswith("_") and callable(value):
                namespace[key] = mcs.log_wrapper(value)
        return super().__new__(mcs, name, bases, namespace)

    @staticmethod
    def log_wrapper(func):
        def wrapper(*args, **kwargs):
            print(f"[LOG] 调用 {func.__name__}")
            r = func(*args, **kwargs)
            return r

        return wrapper


class Calculator(metaclass=LogMeta):
    a = 1

    def add(self, a, b):
        return a + b

    def sub(self, a, b):
        return a - b


calc = Calculator()
print(calc.add(3, 5))
print(calc.sub(10, 4))

# 应该输出:
# [LOG] 调用 add
# 8
# [LOG] 调用 sub
# 6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

About the Author

每个人都有自己得时区,在自己得时区里,一切都是准时的。

BlockSpare — News, Magazine and Blog Addons for (Gutenberg) Block Editor