如何创建元类?

Tri*_*ire 8 python metaclass python-3.x

我粗略地了解了什么是元类.它们是类对象所基于的类(因为类是Python中的对象).但有人可以解释(使用代码)如何创建一个.

Eth*_*man 6

(在这一点上)元类中有两个关键方法:

  • __prepare__,和
  • __new__

__prepare__允许您提供OrderedDict在创建类时用作命名空间的自定义映射(例如).您必须返回您选择的任何名称空间的实例.如果没有实现__prepare__正常dict使用.

__new__ 负责最终课程的实际创作/修改.

一个简单的,无所事事的额外元类看起来像:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)
Run Code Online (Sandbox Code Playgroud)

一个简单的例子:

假设您需要一些简单的验证代码来运行您的属性 - 就像它必须始终是一个int或一个str.没有元类,你的类看起来像:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)
Run Code Online (Sandbox Code Playgroud)

如您所见,您必须重复两次属性的名称.这使得拼写错误以及恼人的错误成为可能.

一个简单的元类可以解决这个问题:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)
Run Code Online (Sandbox Code Playgroud)

这就是元类的样子(不使用,__prepare__因为它不需要):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)
Run Code Online (Sandbox Code Playgroud)

示例运行:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
Run Code Online (Sandbox Code Playgroud)

生产:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
Run Code Online (Sandbox Code Playgroud)

笔记

这个例子很简单,它也可以用类装饰器完成,但可能是一个实际的元类会做得更多.

在Python 2.x中,该__prepare__方法不存在,并且该类通过包含类变量来规定其元类__metaclass__ = ...,如下所示:

class Person(object):
    __metaclass__ = ValidateType
Run Code Online (Sandbox Code Playgroud)

'ValidateType'类供参考:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value
Run Code Online (Sandbox Code Playgroud)