题 什么是Python中的元类?


什么是元类,我们用它们做什么?


4584
2017-09-19 06:10


起源




答案:


元类是类的类。就像类定义了类的实例的行为一样,元类定义了类的行为方式。类是元类的实例。

metaclass diagram

在Python中,您可以为元类使用任意的callables(比如 耶路 显示),更有用的方法实际上是使它成为一个真正的类本身。 type 是Python中常用的元类。如果你想知道,是的, type 它本身就是一个类,它是它自己的类型。你将无法再创造类似的东西 type 纯粹用Python,但Python有点欺骗。要在Python中创建自己的元类,你真的只想要子类 type

元类最常用作类工厂。就像你通过调用类来创建类的实例一样,Python通过调用元类来创建一个新类(当它执行'class'语句时)。结合正常 __init__ 和 __new__ 因此,元类允许您在创建类时执行“额外的事情”,例如使用某些注册表注册新类,或者甚至完全用其他类替换类。

当。。。的时候 class 声明执行后,Python首先执行该体 class 声明作为正常的代码块。生成的命名空间(dict)保存了将要进行的类的属性。元类是通过查看待定类的基类(元类是继承的)来确定的,在 __metaclass__ 要成为的类的属性(如果有的话)或者 __metaclass__ 全局变量。然后使用类的名称,基数和属性调用元类来实例化它。

但是,元类实际上定义了 类型 一个班级,而不仅仅是一个工厂,所以你可以用它们做更多的事情。例如,您可以在元类上定义常规方法。这些元类方法类似于类方法,因为它们可以在没有实例的类上调用,但它们也不像类方法,因为它们不能在类的实例上调用。 type.__subclasses__() 是一个方法的例子 type 元类。您也可以定义正常的“魔术”方法,例如 __add____iter__ 和 __getattr__,实现或改变类的行为方式。

这是比特和碎片的汇总示例:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

2087
2017-09-19 07:01



“你将无法在Python中重新创建纯类型的东西,但是Python欺骗了一些”并非如此。 - ppperry
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b - ppperry


类作为对象

在理解元类之前,您需要掌握Python中的类。 Python对Smalltalk语言借用的类有一个非常奇特的想法。

在大多数语言中,类只是描述如何生成对象的代码片段。在Python中也是如此:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是类比Python更多。类也是对象。

是的,对象。

只要您使用关键字 class,Python执行它并创建 一个东西。指示

>>> class ObjectCreator(object):
...       pass
...

在内存中创建一个名为“ObjectCreator”的对象。

该对象(类)本身能够创建对象(实例), 这就是为什么它是一个班级

但是,它仍然是一个对象,因此:

  • 您可以将其分配给变量
  • 你可以复制它
  • 你可以添加属性
  • 您可以将其作为函数参数传递

例如。:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

由于类是对象,因此您可以像任何对象一样动态创建它们。

首先,您可以使用函数创建一个类 class

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但它不是那么有活力,因为你还是要自己写全班。

由于类是对象,因此它们必须由某些东西生成。

当你使用 class 关键字,Python自动创建此对象。但是作为 对于Python中的大多数东西,它为您提供了一种手动操作方法。

记住这个功能 type?一个很好的旧功能,让你知道什么 键入一个对象是:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

好, type 它具有完全不同的能力,它还可以动态创建类。 type 可以将类的描述作为参数, 并返回一个班级。

(我知道,根据您传递给它的参数,相同的功能可以有两种完全不同的用途,这很愚蠢。这是一个问题,因为向后 Python中的兼容性)

type 这样工作:

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

例如。:

>>> class MyShinyClass(object):
...       pass

可以通过以下方式手动创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

您会注意到我们使用“MyShinyClass”作为类的名称 并作为保存类引用的变量。他们可以是不同的, 但没有理由使事情复杂化。

type 接受字典来定义类的属性。所以:

>>> class Foo(object):
...       bar = True

可以翻译成:

>>> Foo = type('Foo', (), {'bar':True})

并用作普通类:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

当然,你可以继承它,所以:

>>>   class FooChild(Foo):
...         pass

将会:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

最后,您需要为您的班级添加方法。只需定义一个函数 使用正确的签名并将其指定为属性。

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

在动态创建类之后,您可以添加更多方法,就像向正常创建的类对象添加方法一样。

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

你会看到我们要去的地方:在Python中,类是对象,你可以动态地创建一个类。

这就是Python在您使用关键字时所做的事情 class,它通过使用元类来实现。

什么是元类(最后)

元类是创建类的“东西”。

你定义类来创建对象,对吗?

但我们了解到Python类是对象。

好吧,元类是创建这些对象的原因。他们是班级的班级, 你可以这样画出来:

MyClass = MetaClass()
my_object = MyClass()

你已经看到了 type 让你做这样的事情:

MyClass = type('MyClass', (), {})

这是因为功能 type 实际上是一个元类。 type 是个 元类Python用于在幕后创建所有类。

现在你想知道为什么它是用小写写的,而不是 Type

好吧,我想这是一个与之保持一致的问题 str,创造的阶级 字符串对象,和 int创建整数对象的类。 type 是 只是创建类对象的类。

你看到通过检查 __class__ 属性。

一切,我的意思是一切,都是Python中的一个对象。这包括整数, 字符串,函数和类。所有这些都是对象。他们都有 是从一个类创建的:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

现在,是什么 __class__ 任何 __class__ ?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

因此,元类只是创建类对象的东西。

如果您愿意,可以称之为“班级工厂”。

type 是Python使用的内置元类,但当然,你可以创建你的 自己的元类。

__metaclass__ 属性

你可以添加一个 __metaclass__ 写一个类时的属性:

class Foo(object):
    __metaclass__ = something...
    [...]

如果这样做,Python将使用元类来创建类 Foo

小心,这很棘手。

你写 class Foo(object) 首先,但是类对象 Foo 没有创建 在记忆中。

Python会寻找 __metaclass__ 在类定义中。如果找到了, 它将使用它来创建对象类 Foo。如果没有,它将使用 type 创建类。

多读一遍。

当你这样做时:

class Foo(Bar):
    pass

Python执行以下操作:

有没有 __metaclass__ 属性 Foo

如果是的话,在内存中创建一个类对象(我说一个类对象,留在这里),名字 Foo 通过使用中的内容 __metaclass__

如果Python找不到 __metaclass__,它会寻找一个 __metaclass__ 在MODULE级别,并尝试执行相同的操作(但仅适用于不继承任何内容的类,基本上是旧式类)。

然后,如果它找不到任何 __metaclass__ 根本就会使用 Bar's(第一个父级)拥有元类(可能是默认的 type)创建类对象。

这里要小心了 __metaclass__ 属性不会被继承,父类的元类(Bar.__class__) 将会。如果 Bar 用了一个 __metaclass__ 创建的属性 Bar 同 type() (并不是 type.__new__()),子类不会继承该行为。

现在最大的问题是,你能投入什么? __metaclass__ ?

答案是:可以创建一个类的东西。

什么可以创造一个类? type或者任何子类或使用它的东西。

自定义元类

元类的主要目的是自动更改类, 当它被创建。

您通常会在API中执行此操作,您希望在其中创建与之匹配的类 目前的背景。

想象一个愚蠢的例子,你决定模块中的所有类 应该将它们的属性写成大写。有几种方法可以 这样做,但一种方法是设置 __metaclass__ 在模块级别。

这样,将使用此元类创建此模块的所有类, 我们只需告诉元类将所有属性都转换为大写。

幸运的是, __metaclass__ 实际上可以是任何可调用的,它不需要是一个 正式课程(我知道,名字中带有'class'的东西不需要 一个班级,去图......但它很有用)。

因此,我们将从一个简单的例子开始,使用一个函数。

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """

    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

现在,让我们做同样的事情,但是对于元类使用真正的类:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

但这不是真正的OOP。我们称之为 type 直接,我们不会覆盖 或致电父母 __new__。我们开始做吧:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

你可能已经注意到了额外的论点 upperattr_metaclass。有 没什么特别的: __new__ 总是接收它定义的类,作为第一个参数。就像你一样 self 对于接收实例作为第一个参数的普通方法,或者类方法的定义类。

当然,为了清楚起见,我在这里使用的名称很长,但是喜欢 对于 self,所有的论点都有传统的名字。所以真正的生产 元类看起来像这样:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

我们可以通过使用它使它更清洁 super,这将简化继承(因为是的,你可以有元类,继承自元类,继承自类型):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

而已。实际上没有关于元类的更多信息。

使用元类的代码复杂性背后的原因不是因为 这是因为你经常使用元类做扭曲的东西 依靠内省,操纵继承,vars等 __dict__

实际上,元类特别适用于黑魔法,因此 复杂的东西。但它们本身很简单:

  • 拦截班级创作
  • 修改课程
  • 返回修改后的类

为什么要使用元类而不是函数?

以来 __metaclass__ 可以接受任何可调用的,为什么你会使用一个类 因为它显然更复杂?

有几个原因可以这样做:

  • 目的很明确。当你读 UpperAttrMetaclass(type), 你懂 接下来会发生什么
  • 你可以使用OOP。 Metaclass可以从元类继承,覆盖父方法。元类甚至可以使用元类。
  • 如果指定了元类,但没有使用元类函数,则类的子类将是其元类的实例。
  • 您可以更好地构建代码。你从不使用元类作为 像上面的例子那样微不足道。它通常用于复杂的事情。有了 制作多个方法并将它们分组到一个类中的能力非常有用 使代码更容易阅读。
  • 你可以挂钩 __new____init__ 和 __call__。哪个会允许的 你要做不同的事情。即使通常你可以做到这一点 __new__, 有些人使用起来更舒服 __init__
  • 这些被称为元类,该死的!它必须意味着什么!

你为什么要使用元类?

现在是个大问题。为什么要使用一些不起眼的容易出错的功能?

好吧,通常你不会:

元类是更深刻的魔力   99%的用户永远不必担心。   如果你想知道你是否需要它们,   你没有(实际的人   需要他们肯定地知道   他们需要它们,而不需要它们   解释为什么)。

Python大师Tim Peters

元类的主要用例是创建API。一个典型的例子是Django ORM。

它允许您定义如下内容:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

但是如果你这样做:

guy = Person(name='bob', age='35')
print(guy.age)

它不会返回 IntegerField 目的。它将返回一个 int,甚至可以直接从数据库中获取它。

这是可能的,因为 models.Model 定义 __metaclass__ 和 它使用了一些可以扭转局面的魔法 Person 你刚刚用简单的陈述定义 到数据库字段的复杂挂钩。

Django通过公开一个简单的API使复杂的东西变得简单 并使用元类,从此API重新创建代码以完成实际工作 在幕后。

最后一个字

首先,您知道类是可以创建实例的对象。

事实上,类本身就是实例。元类。

>>> class Foo(object): pass
>>> id(Foo)
142630324

一切都是Python中的一个对象,它们都是类的实例 或元类的实例。

除了 type

type 实际上是它自己的元类。这不是你能做到的 在纯Python中重现,并通过在实现中作弊一点来完成 水平。

其次,元类很复杂。您可能不想使用它们 非常简单的课程改动。您可以使用两种不同的技术更改类:

99%的时间你需要改变课程,你最好使用这些。

但是98%的情况下,你根本不需要改变课程。


5763
2017-09-19 06:26



似乎在Django models.Model 它不使用 __metaclass__ 反而 class Model(metaclass=ModelBase): 参考一个 ModelBase然后做上述元类魔法的类。好帖子!这是Django源: github.com/django/django/blob/master/django/db/models/... - Max Goodridge
“这里要小心了 __metaclass__ 属性不会被继承,父类的元类(Bar.__class__) 将会。如果 Bar 用了一个 __metaclass__ 创建的属性 Bar 同 type() (并不是 type.__new__()),子类不会继承那种行为。>> - 你/有人可以解释一下这段话的深度吗? - petrux
@MaxGoodridge这是元类的Python 3语法。看到 Python 3.6数据模型 VS Python 2.7数据模型 - TBBle
这是社区维基的答案(因此,那些评论有更正/改进的人可能会考虑将他们的评论编辑到答案中,如果他们确定它们是正确的话)。 - Shule
这个答案的哪个部分是关于 python2 哪个 pythono3? - styrofoam fly


注意,这个答案适用于Python 2.x,因为它是在2008年编写的,元类在3.x中略有不同,请参阅注释。

元类是使“阶级”工作的秘诀。新样式对象的默认元类称为“类型”。

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

元类需要3个参数。 “名称','基地'和'字典

这是秘密开始的地方。在此示例类定义中查找name,bases和dict的来源。

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

让我们定义一个元类,它将演示如何'类:'打电话给它。

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

现在,一个实际意味着什么的例子,这将自动使列表中的变量“属性”设置在类上,并设置为None。

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

请注意,通过使用元类,“初始化”获得的神奇行为 init_attributes 未传递给Initalised的子类。

这是一个更具体的例子,展示了如何子类化'type'来创建一个在创建类时执行操作的元类。这非常棘手:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b

312
2017-09-19 06:45





元类的一个用途是自动向实例添加新属性和方法。

例如,如果你看一下 Django模型,他们的定义看起来有点令人困惑。看起来好像只是定义了类属性:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

但是,在运行时,Person对象充满了各种有用的方法。见 资源 对于一些惊人的metaclassery。


125
2018-06-21 16:30



是不是使用元类添加新的属性和方法 类 而不是一个实例?据我所知,元类改变了类本身,因此实例可以通过改变的类来不同地构造。对于试图获得元类性质的人来说可能有点误导。通过常规固有可以实现对实例的有用方法。但是,对Django代码的引用是一个很好的例子。 - trixn


其他人已经解释了元类如何工作以及它们如何适合Python类型系统。以下是它们可用于什么的示例。在我编写的测试框架中,我想跟踪定义类的顺序,以便稍后我可以按此顺序实例化它们。我发现使用元类这样做最容易。

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

任何属于它的子类 MyType 然后获取一个类属性 _order 记录定义类的顺序。


117
2017-09-19 06:32





我认为ONLamp对元类编程的介绍写得很好,并且尽管已有几年的历史,却给出了很好的主题介绍。

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

简而言之:类是创建实例的蓝图,元类是创建类的蓝图。可以很容易地看出,Python类中也需要是第一类对象来启用此行为。

我自己从来没有写过,但我认为在网络中可以看到最好用的元类之一 Django框架。模型类使用元类方法来启用编写新模型或表单类的声明式样式。当元类创建类时,所有成员都可以自定义类。

剩下要说的是:如果你不知道什么是元类,你的可能性 不需要它们 是99%。


86
2017-08-10 23:28





什么是元类?你用它们做什么的?

TLDR:元类实例化并定义类的行为,就像类实例化一样,并定义实例的行为。

伪代码:

>>> Class(...)
instance

以上应该看起来很熟悉。那么,哪里呢 Class 来自?它是元类(也是伪代码)的一个实例:

>>> Metaclass(...)
Class

在实际代码中,我们可以传递默认的元类, type,我们需要实例化一个类,我们得到一个类:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

换句话说

  • 类是一个实例,因为元类是一个类。

    当我们实例化一个对象时,我们得到一个实例:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    同样,当我们使用默认元类明确定义一个类时, type,我们实例化它:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • 换句话说,类是元类的实例:

    >>> isinstance(object, type)
    True
    
  • 换句话说,元类是类的类。

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

当您编写类定义并且Python执行它时,它使用元类来实例化类对象(反过来,它将用于实例化该类的实例)。

就像我们可以使用类定义来改变自定义对象实例的行为方式一样,我们可以使用元类定义来改变类对象的行为方式。

它们可以用于什么?来自 文档

元类的潜在用途是无限的。已探索的一些想法包括日志记录,接口检查,自动委托,自动属性创建,代理,框架和自动资源锁定/同步。

尽管如此,除非绝对必要,否则通常鼓励用户避免使用元类。

每次创建类时都使用元类:

当你编写类定义时,例如,像这样,

class Foo(object): 
    'demo'

您实例化一个类对象。

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

它与功能调用相同 type 使用适当的参数并将结果分配给该名称的变量:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

注意,有些东西会自动添加到 __dict__,即名称空间:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

元类 在这两种情况下,我们创建的对象是 type

(关于班级内容的附注 __dict____module__ 是因为类必须知道它们的定义位置,并且 __dict__ 和 __weakref__ 是因为我们没有定义 __slots__ - 要是我们 确定 __slots__ 我们将在实例中节省一些空间,因为我们可以禁止 __dict__ 和 __weakref__ 排除它们。例如:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

......但我离题了。)

我们可以扩展 type 就像任何其他类定义一样:

这是默认值 __repr__ 课程:

>>> Foo
<class '__main__.Foo'>

在编写Python对象时,我们默认可以做的最有价值的事情之一就是为它提供一个好的东西 __repr__。当我们打电话 help(repr) 我们知道有一个很好的测试 __repr__ 这也需要对平等进行测试 - obj == eval(repr(obj))。以下简单实现 __repr__ 和 __eq__ 对于我们的类类的类实例,我们提供了一个可以改进默认值的演示 __repr__ 课程:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

所以现在当我们用这个元类创建一个对象时, __repr__ 在命令行上回显提供了比默认值更不丑陋的视线:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

很好 __repr__ 为类实例定义,我们有更强的调试代码的能力。但是,进一步检查 eval(repr(Class)) 不太可能(因为函数将无法从默认值中评估) __repr__的)。

预期用途: __prepare__ 命名空间

例如,如果我们想知道创建类的方法的顺序,我们可以提供一个有序的dict作为类的命名空间。我们会这样做 __prepare__ 哪一个 如果在Python 3中实现,则返回该类的命名空间dict

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

用法:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

现在我们记录了这些方法(和其他类属性)的创建顺序:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

注意,这个例子改编自 文件 - 新的 enum在标准库中 做这个。

所以我们所做的是通过创建一个类来实例化一个元类。我们也可以像对待任何其他类一样对待元类。它有一个方法解析顺序:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

它大致正确 repr (除非我们能找到代表我们功能的方法,否则我们不能再评估。):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

74
2018-03-01 19:48