python的元类是什么

什么是python元类

类是为了创建对象,元类是为了创建类。

本文主要来自stackoverflow

python中的类

在理解元类之前,我们需要掌握 python 中的类。在大多数语言中,类知识描述如何产生对象的代码段。
在python中也是如此:

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

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

但是在python中,类的概念有了更多的扩展。还记得python中常说的 万物皆对象 嘛,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

>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是这不够动态,因为你让然必须自己编写整个类。

由于类是对象,因此它们必须有某种东西自动生成。

使用 class 关键字时, Python 会自动创建此对象。但是,与 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 可以将类的描述作为参数,并返回一个类。

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)

>>> 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__

>>> name = 'bob'
>>> name.__class__

>>> def foo(): pass
>>> foo.__class__

>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__

现在,__class____class__属性是什么呢?

>>> age.__class__.__class__

>>> name.__class__.__class__

>>> foo.__class__.__class__

>>> b.__class__.__class__

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

如果愿意,可以称其为 ‘class factory’。
type 是Python 中内置的一个元类。当然,你也可以创建你自己的元类。

__metaclsss__属性

在Python2 中,你可以添加__metaclass__属性当你在创建类的时候,

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

如果你这样做了,Python 会使用元类去创建 Foo 这个类。

小心,这很棘手(实在不知道怎么翻译)

你首先写下代码class Foo(object),但是在内存中此时Foo对象并未创建。

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

想想看,当你项如下这么操作时,python会做什么?

class Foo(Bar):
    pass

Foo中有__metaclass__属性吗?
如果有,在内存中就会创建一个类对象(我说的是类对象,just follow me), 是使用 __metaclass__ 创建的名为Foo的类对象。

如果Python没有找到__metaclass__。它将在模块级别中查找一个元类,并像上面那样做的一样。(但是这仅限于不继承任何内容的类,基本上他们属于旧式类)

这段不会翻译,附原文如下:If Python can’t find metaclass, it will look for a metaclass at the MODULE level, and try to do the same (but only for classes that don’t inherit anything, basically old-style classes).

如果 Python 没有找到任何元类,将会使用Bar(第一个父类)的元类(它可能是type)来创建类对象。

在这类要小心的是,__metaclass__属性不能够被继承,这个元类只能够是它的父类(Bar.__class__)的。如果Bar是通过type__metaclass__属性来创建的,而不是type.__new__(),那么子类将不会继承该行为。

看不懂,翻译的贼差劲

现在最大的问题是,你可以在 __metaclass__中放进去什么?

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

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

python3中的元类

设置元类的方法在 python3 中已经更改。

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

即不再使用__metaclass__属性,而是在基类列表中使用关键字参数。

但是元类的基本行为保持不变。

在python 3中添加到元类时,您可以将属性作为关键字参数传递给元类,如下所示:

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

自定义元类

Custom metaclasses
The main purpose of a metaclass is to change the class automatically, when it’s created.

You usually do this for APIs, where you want to create classes matching the current context.

Imagine a stupid example, where you decide that all classes in your module should have their attributes written in uppercase. There are several ways to do this, but one way is to set __metaclass__ at the module level.

This way, all classes of this module will be created using this metaclass, and we just have to tell the metaclass to turn all attributes to uppercase.

Luckily, __metaclass__ can actually be any callable, it doesn’t need to be a formal class (I know, something with ‘class’ in its name doesn’t need to be a class, go figure… but it’s helpful).

So we will start with a simple example, by using a function.

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

Now, let’s do exactly the same, but using a real class for a metaclass:

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

But this is not really OOP. We call type directly and we don’t override or call the parent __new__. Let’s do it:

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)

You may have noticed the extra argument upperattr_metaclass. There is nothing special about it:__new__ always receives the class it’s defined in, as first parameter. Just like you have self for ordinary methods which receive the instance as first parameter, or the defining class for class methods.

Of course, the names I used here are long for the sake of clarity, but like for self, all the arguments have conventional names. So a real production metaclass would look like this:

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)

We can make it even cleaner by using super, which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type):

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)

Oh, and in python 3 if you do this call with keyword arguments, like this:

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

It translates to this in the metaclass to use it:

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

That’s it. There is really nothing more about metaclasses.

The reason behind the complexity of the code using metaclasses is not because of metaclasses, it’s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.

Indeed, metaclasses are especially useful to do black magic, and therefore complicated stuff. But by themselves, they are simple:

  • intercept a class creation
  • modify the class
  • return the modified class

Why would you use metaclasses classes instead of functions?

Since __metaclass__ can accept any callable, why would you use a class since it’s obviously more complicated?

There are several reasons to do so:

  • The intention is clear. When you read UpperAttrMetaclass(type), you know what’s going to follow
  • You can use OOP. Metaclass can inherit from metaclass, override parent methods. Metaclasses can even use metaclasses.
  • Subclasses of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.
  • You can structure your code better. You never use metaclasses for something as trivial as the above example. It’s usually for something complicated. Having the ability to make several methods and group them in one class is very useful to make the code easier to read.
  • You can hook on __new__, __init__ and __call__. Which will allow you to do different stuff. Even if usually you can do it all in __new__, some people are just more comfortable using __init__.
    These are called metaclasses, damn it! It must mean something!

Why would you use metaclasses?

Now the big question. Why would you use some obscure error prone feature?

Well, usually you don’t:

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 Guru Tim Peters

The main use case for a metaclass is creating an API. A typical example of this is the Django ORM.

It allows you to define something like this:

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

But if you do this:

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

It won’t return an IntegerField object. It will return an int, and can even take it directly from the database.

This is possible because models.Model defines __metaclass__ and it uses some magic that will turn the Person you just defined with simple statements into a complex hook to a database field.

Django makes something complex look simple by exposing a simple API and using metaclasses, recreating code from this API to do the real job behind the scenes.

The last word

First, you know that classes are objects that can create instances.

Well in fact, classes are themselves instances. Of metaclasses.

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

Everything is an object in Python, and they are all either instances of classes or instances of metaclasses.

Except for type.

type is actually its own metaclass. This is not something you could reproduce in pure Python, and is done by cheating a little bit at the implementation level.

Secondly, metaclasses are complicated. You may not want to use them for very simple class alterations. You can change classes by using two different techniques:

  • monkey patching
  • class decorators

99% of the time you need class alteration, you are better off using these.

But 98% of the time, you don’t need class alteration at all.

你可能感兴趣的:(python学习,python)