天天一道 python 面试题 - Python中的元类(metaclass) 详细版本

类做为对象

在理解元类以前,您须要掌握Python的类。Python从Smalltalk语言中借用了一个很是特殊的类概念。python

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

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

可是类比Python中的更多。类也是对象。ide

一旦使用关键字class,Python就会执行它并建立一个对象函数

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

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

这个对象(类)自己具备建立对象(实例)的能力,这就是为何它是一个类spa

可是,它仍然是一个对象,所以:翻译

  • 您能够将其分配给变量
  • 你能够复制它
  • 您能够为其添加属性
  • 您能够将其做为函数参数传递

例如:code

>>> 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如下方法在函数中建立一个类:blog

>>> 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, bases, attrs)
  • name:班级名称
  • bases:父类的元组(对于继承,能够为空)
  • attrs:包含属性名称和值的字典

例如:

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

最终,您须要向类中添加方法。只需定义具备适当签名的函数并将其分配为属性便可

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

在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执行如下操做:

中有__metaclass__属性Foo吗?

若是是,请在内存中建立一个类对象(我说一个类对象,在这里呆在一块儿),并Foo使用in中的名称__metaclass__

若是Python找不到__metaclass__,它将__metaclass__在MODULE级别查找,并尝试执行相同的操做(但仅适用于不继承任何内容的类,基本上是老式的类)。

而后,若是根本找不到任何对象__metaclass__,它将使用Bar的(第一个父对象)本身的元类(多是默认值type)建立类对象。

请注意,该__metaclass__属性将不会被继承,而父(Bar.__class__)的元类将被继承。若是Bar使用经过(而不是)__metaclass__建立的属性,则子类将不会继承该行为。Bar`type()`type.__new__()

如今最大的问题是,您能够输入__metaclass__什么?

答案是:能够建立类的东西。

什么能够建立一个类?type,或任何继承或使用它的内容。

Python 3中的元类

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

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

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

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

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

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

阅读如下部分,了解python如何处理此问题。

自定义元类

元类的主要目的是在建立类时自动更改它。

一般,您要对API进行此操做,在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_attrs):    """      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_attrs = {        attr if attr.startswith("__") else attr.upper(): v        for attr, v in future_class_attrs.items()    }    # let `type` do the class creation    return type(future_class_name, future_class_parents, uppercase_attrs)__metaclass__ = upper_attr # this will affect all classes in the moduleclass 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'

让咱们检查:

>>> hasattr(Foo, 'bar')False>>> hasattr(Foo, 'BAR')True>>> Foo.BAR'bip'

如今,让咱们作彻底同样的操做,可是对元类使用真实的类:

# remember that `type` is actually a class like `str` and `int`# so you can inherit from itclass 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_attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in future_class_attrs.items()        }        return type(future_class_name, future_class_parents, uppercase_attrs)

让咱们重写上面的内容,可是如今有了更短,更实际的变量名,咱们知道它们的含义了:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type(clsname, bases, uppercase_attrs)

您可能已经注意到了额外的争论cls。它没有什么特别的:__new__始终将其定义的类做为第一个参数。就像您有self将实例做为第一个参数接收的普通方法同样,仍是为类方法定义了类。

但这不是适当的OOP。咱们正在type直接致电,而不是覆盖或致电父母的__new__。让咱们改成:

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return type.__new__(cls, clsname, bases, uppercase_attrs)

经过使用super,咱们可使其更加整洁,这将简化继承(由于是的,您能够具备元类,从元类继承,从类型继承):

class UpperAttrMetaclass(type):    def __new__(cls, clsname, bases, attrs):        uppercase_attrs = {            attr if attr.startswith("__") else attr.upper(): v            for attr, v in attrs.items()        }        return super(UpperAttrMetaclass, cls).__new__(            cls, clsname, bases, uppercase_attrs)

在python 3中,若是您使用关键字参数进行此调用,例如:

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

它将在元类中转换为使用它:

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

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

使用元类编写代码的复杂性背后的缘由不是由于元类,而是由于您一般使用元类依靠自省,操纵继承和诸如var之类的变量来作扭曲的事情__dict__

确实,元类对于作黑魔法特别有用,所以也很复杂。但就其自己而言,它们很简单:

  • 拦截class建立
  • 修改class
  • 返回修改后的类

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

既然__metaclass__能够接受任何可调用对象,那么为何要使用一个类,由于它显然更复杂?

这样作有几个缘由:

  • 意图很明确。阅读时UpperAttrMetaclass(type),您会知道接下来会发生什么
  • 您可使用OOP。元类能够继承元类,重写父方法。元类甚至可使用元类。
  • 若是您指定了元类类,但没有元类函数,则该类的子类将是其元类的实例。
  • 您能够更好地构建代码。绝对不要像上面的示例那样将元类用于琐碎的事情。一般用于复杂的事情。可以制做几种方法并将它们分组在一个类中的能力对于使代码更易于阅读很是有用。
  • 您能够勾上__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()

可是,若是您这样作:

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

它不会返回IntegerField对象。它将返回int,甚至能够直接从数据库中获取它。

这是可能的,由于models.Modeldefine __metaclass__并使用了一些魔术,这些魔术将使Person您使用简单的语句定义的对象变成与数据库字段的复杂挂钩。

Django经过公开一个简单的API并使用元类,从该API从新建立代码来完成幕后的实际工做,使看起来复杂的事情变得简单。

最后一个字

首先,您知道类是能够建立实例的对象。

实际上,类自己就是实例。元类。

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

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

除了type

type其实是它本身的元类。这不是您能够在纯Python中复制的东西,而是经过在实现级别上做弊来完成的。

其次,元类很复杂。您可能不但愿将它们用于很是简单的类更改。您可使用两种不一样的技术来更改类:

欢迎点赞,收藏,关注,三连击,谢谢,今天文章就到此结束了

相关文章
相关标签/搜索