在理解元类以前,您须要掌握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中已更改:
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__
。
确实,元类对于作黑魔法特别有用,所以也很复杂。但就其自己而言,它们很简单:
既然__metaclass__
能够接受任何可调用对象,那么为何要使用一个类,由于它显然更复杂?
这样作有几个缘由:
UpperAttrMetaclass(type)
,您会知道接下来会发生什么__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.Model
define __metaclass__
并使用了一些魔术,这些魔术将使Person
您使用简单的语句定义的对象变成与数据库字段的复杂挂钩。
Django经过公开一个简单的API并使用元类,从该API从新建立代码来完成幕后的实际工做,使看起来复杂的事情变得简单。
首先,您知道类是能够建立实例的对象。
实际上,类自己就是实例。元类。
>>> class Foo(object): pass>>> id(Foo)142630324
一切都是Python中的对象,它们都是类的实例或元类的实例。
除了type
。
type
其实是它本身的元类。这不是您能够在纯Python中复制的东西,而是经过在实现级别上做弊来完成的。
其次,元类很复杂。您可能不但愿将它们用于很是简单的类更改。您可使用两种不一样的技术来更改类:
欢迎点赞,收藏,关注,三连击,谢谢,今天文章就到此结束了