Python中的元类是什么?

7323

什么是元类?它们有什么用途?

25个回答

64

简短版

type(obj) 函数返回对象的类型。

type() 函数对于一个类来说,它的元类就是其 metaclass

要使用元类:

class Foo(object):
    __metaclass__ = MyMetaClass

type 是它自己的元类。一个类的类是元类--一个类的主体是传递给用于构造类的元类的参数。

在这里,你可以阅读有关如何使用元类来自定义类构造的内容。


59

type 实际上是一个 元类 —— 一种创建其他类的类。 大多数 元类 都是 type 的子类。这个 元类 将新建的类作为其第一个参数来接收,并提供了访问类对象及其详细信息的途径,如下所述:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

注意:

请注意,该类在任何时候都没有被实例化;仅仅是创建该类的简单行为就会触发元类的执行。


42

Python类本身就是对象,就像实例一样,它们属于它们的元类。

默认元类在您将类定义为以下内容时应用:

class foo:
    ...

定义元类时,需要继承type并覆盖以下魔法方法来插入自己的逻辑。

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

无论如何,这两个钩子是最常用的。元类编程非常强大,上述并不是所有元类编程用途的详尽列表。


41

type()函数可以返回一个对象的类型或创建一个新类型,

例如,我们可以使用type()函数创建一个Hi类,并且不需要使用这种方式:class Hi(object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

除了使用type()动态创建类之外,您还可以控制类的创建行为并使用元类。

根据Python对象模型,类是对象,因此类必须是另一个特定类的实例。 默认情况下,Python类是type类的实例。也就是说,type是大多数内置类和用户定义类的元类。

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

当我们在元类中传递关键字参数时,魔法将会生效,它告诉Python解释器通过ListMetaclass来创建CustomList。在new()中,此时我们可以修改类定义,例如添加一个新的方法,然后返回修改后的定义。


30

除了已经发布的答案之外,我可以说一个元类(metaclass)定义了一个类的行为方式。所以,您可以显式地设置您的元类。每当Python获取关键字 class 时,它就开始搜索元类。如果没有找到-则使用默认的元类类型来创建该类的对象。使用__metaclass__属性,您可以设置您的类的元类

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

它将会生成如下输出:

class 'type'

当然,你可以创建自己的metaclass来定义使用你的类创建的任何类的行为。

要做到这一点,必须继承您的默认metaclass类型类,因为这是主要的metaclass

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

输出结果将会是:

class '__main__.MyMetaClass'
class 'type'

21
请注意,在Python 3.6中引入了一个新的双下划线方法__init_subclass__(cls, **kwargs),以替换元类的许多常见用例。当定义的类的子类被创建时,它会被调用。请参阅Python文档

20

以下是它的另一个用法示例:

  • 您可以使用metaclass来更改其实例(即类)的功能。
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

元类非常强大,它可以做很多事情(比如猴子魔法),但是要小心这些只能由你知道。


19

顶部答案是正确的

但是读者可能会在这里搜索有关类似命名的内部类的答案。它们存在于流行的库中,例如DjangoWTForms

正如DavidW在本回答下面的评论中指出的那样,这些都是 特定于库 的功能,不能与具有类似名称的不相关的 Python语言特性混淆

相反,这些是类字典中的命名空间。它们使用内部类构造,以增加可读性。

在这个示例特殊字段中, abstract与Author模型的字段明显分开。

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()

    class Meta:
        abstract = True

另一个例子来自于WTForms的文档:

from wtforms.form import Form
from wtforms.csrf.session import SessionCSRF
from wtforms.fields import StringField

class MyBaseForm(Form):
    class Meta:
        csrf = True
        csrf_class = SessionCSRF

    name = StringField("name")

这个语法在 Python 编程语言中并没有特殊的处理。在这里,Meta 不是一个关键字,也不会触发元类行为。相反,像 DjangoWTForms 这样的第三方库代码会读取某些类的构造函数和其他地方的此属性。

声明的存在会修改具有这些声明的类的行为。例如,WTForms 会读取 self.Meta.csrf 来确定表单是否需要一个 csrf 字段。


2
这是Django特定的功能,其中一个名为Meta的嵌套类具有特殊含义。问题涉及到一个与之类似名称的不相关的Python语言特性。 - DavidW
@DavidW — hamilyon 对这篇文章进行了英勇的编辑。在我看来,现在它是一个非常有用的回答。 - Alex Waygood
1
@AlexWaygood 我可能会拒绝这个编辑(改动太大了...),但我可以看出它澄清了一些令人困惑的事情,所以它可能是有用的。考虑到这一点,我已经取消了我的反对票。 - DavidW
@DavidW 是的,我认为你可以从两个方面来争论。我通常不会批准那么大的编辑。但我觉得它保持了原帖的精神,并且似乎已经付出了相当多的努力去澄清一个合理的困惑点,所以决定批准。 - Alex Waygood

17
在Python中,类是一个对象,就像任何其他对象一样,它是"某物"的实例。这个"某物"被称为元类。这个元类是一种特殊类型的类,用于创建其他类的对象。因此,元类负责创建新类。这使得程序员可以自定义生成类的方式。
通常通过覆盖new()和init()方法来创建元类。new()方法可以被重写以改变对象的创建方式,而init()方法可以被重写以改变对象初始化的方式。可以通过多种方式创建元类。其中一种方式是使用type()函数。当使用三个参数调用type()函数时,它会创建一个元类。这些参数是:
1. 类名 2. 元组,其中包含类继承的基类 3. 包含所有类方法和类变量的字典
另一种创建元类的方式是使用'metaclass'关键字。将元类定义为一个简单的类。在继承类的参数中,传递metaclass = metaclass_name 元类可以特别用于以下情况:
1. 应将特定效果应用于所有子类时 2. 需要在创建时自动更改类 3. API开发人员使用

17

在面向对象编程中,元类是其实例为类的一种类。正如普通类定义了某些对象的行为一样,元类定义了某些类及其实例的行为。

元类这个术语简单地说就是用于创建类的东西。换句话说,它是类的类。元类用于创建类,就像对象是类的实例一样,类是元类的实例。在Python中,类也被认为是对象。


6
如果您添加了一些例子,而不是仅给出书本式定义,那将更好。您回答的第一行似乎是从Metaclasses的维基百科页面复制的。 - verisimilitude
3
@verisimilitude 我也在学习,你能否通过提供一些你的经验实际例子来帮助我改进这个答案? - Venu Gopal Tewari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接