什么是Python中的元类?

2024-04-26 14:27:48 发布

您现在位置:Python中文网/ 问答频道 /正文


Tags: python
3条回答

注意,这个答案是针对Python 2.x的,因为它是在2008年编写的,3.x中的元类略有不同。

元类是使“类”起作用的秘密调味料。新样式对象的默认元类称为“type”。

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

元类需要3个参数名称'、''和'字典'

秘密就从这里开始。在这个示例类定义中查找名称、基和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'

现在,一个实际意义重大的例子,它会自动将列表“attributes”中的变量设置在类上,并设置为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

注意,Initialised通过拥有元类而获得的神奇行为init_attributes不会传递给Initialised的子类。

下面是一个更具体的例子,展示了如何将“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

类作为对象

在理解元类之前,您需要掌握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中的大多数内容,它提供了一种手动执行的方法。

还记得功能吗?让你知道 对象类型为:

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

嗯,^{}具有完全不同的能力,它还可以动态创建类。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使用的内置元类,当然,您可以创建 自己的元类。

^{}属性

在Python 2中,编写类时可以添加__metaclass__属性(Python 3语法请参见下一节):

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

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

小心,这很棘手。

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

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

读几遍。

当你这样做时:

class Foo(Bar):
    pass

Python执行以下操作:

Foo中是否有__metaclass__属性?

如果是,请在内存中创建一个名为Foo的类对象(我说的是一个类对象,请留在这里),方法是使用__metaclass__中的内容。

如果Python找不到__metaclass__,它将在模块级查找__metaclass__,并尝试执行相同的操作(但仅限于不继承任何内容的类,基本上是旧式类)。

如果它根本找不到任何__metaclass__,它将使用Bar(第一个父类)自己的元类(可能是默认的type)来创建类对象。

请注意,这里的__metaclass__属性不会被继承,父类(Bar.__class__)的元类将被继承。如果Bar使用了一个__metaclass__属性,该属性用type()(而不是type.__new__())创建了Bar,则子类将不会继承该行为。

现在最大的问题是,你能在__metaclass__中放些什么?

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

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

Python 3中的元类

设置元类的语法在Python3中已更改:

class Foo(object, metaclass=something):
    ...

也就是说,不再使用__metaclass__属性,而是使用基类列表中的关键字参数。

但是,元类的行为仍然是largely the same

python 3中添加到元类的一件事是,您还可以将属性作为关键字参数传递到元类中,如下所示:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

阅读下面的一节了解python如何处理这个问题。

自定义元类

元类的主要目的是自动更改类, 当它被创造出来的时候。

您通常为api执行此操作,在api中创建与 当前上下文。

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

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

幸运的是,__metaclass__实际上可以是任何可调用的,它不需要是 正式的类(我知道,名字中有“类”的东西不需要 一个班,想办法。。。但这很有帮助)。

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

# 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)

但这并不是很糟糕。我们直接调用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)

哦,在python 3中,如果使用关键字参数执行此调用,如下所示:

class Foo(object, metaclass=Thing, kwarg1=value1):
    ...

它在元类中转换成这个来使用它:

class Thing(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

就这样。关于元类真的没有什么了。

使用元类的代码之所以复杂,并不是因为 关于元类,这是因为你通常使用元类来做扭曲的事情 依赖内省、操纵继承、变量如__dict__

确实,元类在执行黑魔法时特别有用,因此 复杂的东西。但就其本身而言,它们很简单:

  • 拦截类创建
  • 修改类
  • 返回修改后的类

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

既然__metaclass__可以接受任何可调用的,为什么要使用类 因为事情显然更复杂?

这样做有几个原因:

  • 意图是明确的。当你阅读UpperAttrMetaclass(type)时,你知道 接下来会发生什么
  • 你可以使用OOP。元类可以从元类继承,重写父方法。元类甚至可以使用元类。
  • 如果指定了元类,则类的子类将是其元类的实例,但不能使用元类函数。
  • 您可以更好地构造代码。你从不使用元类 就像上面的例子一样微不足道。通常是为了一些复杂的事情。拥有 能够生成多个方法并将它们分组到一个类中是非常有用的 使代码更容易阅读。
  • 您可以挂接__new____init____call__。这将允许 你要做不同的事。即使你通常都能在__new__中完成, 有些人更喜欢使用__init__
  • 这些叫做元类,该死!一定有什么意义!

为什么要使用元类?

现在是个大问题。为什么你会使用一些模糊的易出错特性?

通常你不会:

Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why).

Python大师蒂姆·彼得斯

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

它允许您定义如下内容:

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

但如果你这样做:

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

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

这是可能的,因为models.Model定义了__metaclass__和 它使用了一些魔法,可以将刚才用简单语句定义的Person 连接到数据库字段的复杂钩子。

Django通过公开一个简单的API使复杂的事情看起来很简单 使用元类,从这个API重新创建代码来完成真正的工作 在幕后。

最后一句话

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

实际上,类本身就是实例。元类的。

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

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

除了type

type实际上是它自己的元类。这不是你能做到的 在纯Python中复制,并通过欺骗一点点的实现 水平。

其次,元类是复杂的。你可能不想用它们 非常简单的班级变动。您可以使用两种不同的技术来更改类:

99%的时候你需要修改课程,你最好用这些。

但98%的时候,你根本不需要改变班级。

元类是类的类。类定义类的实例(即对象)的行为,而元类定义类的行为。类是元类的实例。

在Python中,可以对元类使用任意的可调用项(如Jerub所示),更好的方法是使其成为实际的类本身。type是Python中常见的元类。type本身就是一个类,它是自己的类型。纯用Python无法重新创建类似type的内容,但Python有点作弊。要在Python中创建自己的元类,您只需要将type子类化。

元类最常用作类工厂。当通过调用类创建对象时,Python通过调用元类创建一个新类(当它执行“class”语句时)。因此,与普通的__init____new__方法相结合,元类允许您在创建类时执行“额外的操作”,比如在某些注册表中注册新类,或者用其他完全替换类。

当执行class语句时,Python首先将class语句的主体作为普通代码块执行。结果命名空间(dict)保存类的属性。元类是通过查看要成为类的基类(元类是继承的)、要成为类的__metaclass__属性(如果有的话)或__metaclass__全局变量来确定的。然后使用类的名称、基和属性调用元类来实例化它。

然而,元类实际上定义了类的类型,而不仅仅是类的工厂,因此您可以对它们做更多的工作。例如,可以在元类上定义普通方法。这些元类方法类似于类方法,因为它们可以在没有实例的类上调用,但它们也不同于类方法,因为它们不能在类的实例上调用。type.__subclasses__()type元类上的一个方法示例。您还可以定义普通的“magic”方法,例如__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__

相关问题 更多 >