当前位置: 首页 > 科技观察

理解python的元类(metaclass)

时间:2023-03-21 13:08:14 科技观察

前言这篇博客是我在看了一个stackoverflow问题的回复后写的。例子基本使用e-satis自带的例子,语言组织基本以翻译为主。但我不是翻译,也没有严格遵守每行每句的翻译;有时我会改变表达的顺序并省略一些我认为无关紧要的词,以便读者更好地理解它们。所以,如果你不喜欢我的语言表达,或者想看英文原文,可以点此链接查看原回复。类也是对象。在了解元类之前,我们首先要掌握python中的类(class)是什么。python中类的概念是从smalltalk语言中借用的。在大多数语言中,类指的是一段“描述如何生成一个对象(object)”的代码,对于python也是如此。>>>classObjectCreator(object):...pass...>>>my_object=ObjectCreator()>>>print(my_object)<__main__.ObjectCreatorobjectat0x8974f2c>然而在python中,类远不止于此,类也是对象。遇到关键字class,python会自动执行并生成一个对象。在以下代码段中:>>>classObjectCreator(object):...pass...python在内存中创建一个名为“ObjectCreator”的对象。这个对象(类)本身具有生成对象(实例实例)的能力。这就是为什么叫这个东西(后面遇到混淆的地方,我们就叫它:类对象)也是类的原因。同时,它也是一个对象,所以可以对它进行如下操作:赋值给一个变量,复制它,给它添加属性,作为参数传递给一个函数例子:>>>print(ObjectCreator)#可以打印一个类,因为它也是一个对象>>>defecho(o):...print(o)...>>>echo(ObjectCreator)#将该值作为参数传递给函数>>>print(hasattr(ObjectCreator,'new_attribute'))False>>>ObjectCreator.new_attribute='foo'#youcanaddattributestoaclass>>>print(hasattr(ObjectCreator,'new_attribute'))True>>>print(ObjectCreator.new_attribute)foo>>>ObjectCreatorMirror=ObjectCreator#将类赋值给变量>>>print(ObjectCreatorMirror.new_attribute)foo>>>print(ObjectCreatorMirror())<__main__.ObjectCreatorobjectat0x8997b4c>动态创建类由于类是也是一个对象,那么我们就可以在运行时创建它,就像创建一个对象一样自然。首先,我们使用class关键字定义一个生成类的函数:>>>defchoose_class(name):...ifname=='foo':...classFoo(object):...pass...returnFoo#returntheclass,notaninstance...else:...classBar(object):...pass...returnBar...>>>MyClass=choose_class('foo')>>>print(MyClass)#thefunctionreturnsaclass,notaninstance>>>print(MyClass())#youcancreateanobjectfromthisclass<__main__.Fooobjectat0x89c6d4c>这个很好理解。然而,这并不是那么动态。我们仍然需要自己编写这个类的代码。既然一个类也是一个对象,那么就应该有一个用来生成它的东西。这东西是类型。先说说你知道的类型吧。这个古老而有用的函数让我们知道一个对象是什么类型。>>>print(type(1))>>>print(type("1"))>>print(type(ObjectCreator))>>>print(type(ObjectCreator()))其实type有一个完全不同的功能,可以在运行时生成类。type可以传入一些参数,然后返回一个类。(好吧,必须承认,同一个函数类型根据不同的传入参数有两个完全不同的函数是很愚蠢的。但是python这样做是为了保持向后兼容性。)下面的例子创建类型类的用法。首先类一般定义如下:>>>classMyShinyClass(object):...pass在下面,MyShinyClass也可以这样创建,和上面的创建方法性能一样:>>>MyShinyClass=type('MyShinyClass',(),{})#returnssaclassobject>>>print(MyShinyClass)>>>print(MyShinyClass())#createaninstancewiththeclass<__main__.MyShinyClassobjectat0x8997cec>类型需要传递给创建类输入三个参数,分别是:类的名称“类的父类”的一组元组(元组)(这将实现继承,也可以为空)字典(属性类的名称和值,以key-value形式存在,不传相当于为空,如一般写法中的pass)。让我们做一些复杂的事情来更好地理解按类型传递的三个参数:classFoo(object):bar=Truedefecho_bar(self):print(self.bar)等同于:defecho_bar(self):print(self.bar)Foo=type('Foo',(),{'bar':True,'echo_bar':echo_bar})想看继承关系类的实现,这里:classFooChild(Foo):pass相当于:FooChild=type('FooChild',(Foo,),{})回顾我们学到的东西:在python中,类是对象,您可以在运行时动态创建类。那么究竟什么是元类(metaclass)元类就是创建类的人。(其实type就是一个元类)我们知道我们定义class是为了能够创建对象吧?我们还了解到python中的类也是对象。嗯,元类是用来创建“类对象”的类。它是“类对象”的“类”。可以这样理解:MyClass=MetaClass()MyObject=MyClass()也可以用我们上面学习的类型来表示:MyClass=type('MyClass',(),{})说白了就是函数类型是一个特殊的metaclass.python在幕后使用类型来创建所有类。type是所有类的元类。我们可以使用__class__属性来验证这个声明。在python中,一切都是对象:整数、字符串、函数、类。所有这些对象都是通过类创建的。>>>age=35>>>age.__class__>>>name='bob'>>>name.__class__>>>deffoo():通过>>>富。__class__>>>classBar(object):pass>>>b=Bar()>>>b.__class__那么,__class__的__class__是什么?>>>age.__class__.__class__>>>name.__class__.__class__>>>foo.__class__.__class__>>>b.__class__。__class__元类是创建类对象的工具。如果你愿意,你也可以称它为“类工厂”。type是python的内置元类。但是,您也可以编写自己的metaclass.__metaclass__属性我们可以将__metaclass__属性添加到class.classFoo(object):__metaclass__=something...[...]当您这样做时,python将使用元类来创建类:呸。注意,这里有一些技巧。当你写classFoo(object)时,类对象Foo还没有在内存中生成。Python将在类定义中查找__metaclass__。如果找到,python将使用这个__metaclass__创建类对象:Foo。如果没有找到,python使用类型创建Foo。请多次重复以下段落:当您编写以下代码时:classFoo(Bar):passpython执行以下操作:Foo是否具有__metaclass__属性?如果是这样,python会在内存中通过__metaclass__创建一个名为Foo的类对象。如果python在Foo中没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__,并像以前一样尝试。如果python在自下而上遍历父类时找不到__metaclass__,它会在模块中寻找__metaclass__并尝试这样做。如果仍然找不到__metaclass__,python将使用内置类型(这也是一个元类)来创建此类对象。现在的问题是,我们如何在代码中实现__metaclass__呢?随便写一些可以用来生成类的东西。那么什么可以生成类呢?毫无疑问,它是类型,或类型的任何子类,或任何使用类型的东西。自定义元类使用元类的主要目的是能够在创建类时自动修改类。一个愚蠢的需求,我们决定将这个模块中所有类的属性都改为大写。有几种方法可以做到这一点,这里我们使用__metaclass__来做到这一点。在模块级别定义元类,模块中的所有类都将使用它来创建类。我们只需要告诉元类将所有属性转换为大写。#type也是一个类,我们可以继承它。classUpperAttrMetaclass(type):#__new__是在__init__之前调用的一个特殊方法#__new__用于创建一个对象并返回这个对象#而__init__只是将输入参数初始化为对象#其实你很少会用到__new__,除非你希望能够控制对象的创建#这里,类就是我们要创建的对象,我们希望能够自定义它,所以我们重写了__new__#如果你愿意,你也可以在__init__中做一些事情#有一些高级用法涉及重写__call__,但我们不会在这里这样做。def__new__(upperattr_metaclass,future_class_name,future_class_parents,future_class_attr):uppercase_attr={}forname,valinfuture_class_attr.items():ifnotname.startswith('__'):uppercase_attr[name.upper()]=valelse:uppercase_attr[name]=valreturntype(future_class_name,future_class_parents,uppercase_attr)这里其实不是OOP(面向对象编程)。因为我们直接调用类型而不是重写父类的__type__方法。所以我们也可以这样处理:upper()]=valelse:uppercase_attr[name]=valreturntype.__new__(upperattr_metaclass,future_class_name,future_class_parents,uppercase_attr)这样,我们只是复用了type.__new__方法,也就是我们熟悉的基本OOP编程,并没有什么魔法可言。您可能会注意到__new__方法与type(future_class_name,future_class_parents,future_class_attr)相比多了一个参数:upperattr_metaclass,请不要担心,这没什么特别的:__new__总是将“它要定义的类”作为第一个参数,这就像类的一般方法(method)中的self一样,也是作为第一个参数传入的。当然,这里的名字对我来说确实太长了。像self一样,所有参数都有它们的传统名称。所以,在实际代码中,一个元类应该这样写:(为了代码更清晰,我们也使用普通的super)classUpperAttrMetaclass(type):def__new__(cls,clsname,bases,attrs):uppercase_attr={}forname,valinattrs.items():ifnotname.startswith('__'):uppercase_attr[name.upper()]=valelse:uppercase_attr[name]=valreturnsuper(UpperAttrMetaclass,cls).__new__(cls,clsname,bases,attrs)使用元类的代码比较复杂,但我们之所以使用它并不是为了复杂,而是因为我们通常使用元类来做一些晦涩难懂的事情,比如依赖内省,控制继承等。确实,它特别有用用元类做一些“黑魔法”,从而使代码复杂化。但就元类本身而言,其实很简单:中断类的默认创建,修改类,最后返回修改后的类。为什么要使用元类现在我们面临一个问题:为什么要使用元类?这很容易错误和晦涩难懂。好吧,一般来说,我们根本不使用它,99%的用户根本不应该理会它。实际使用元类的人确切地知道他们需要做什么,并且不需要解释他们为什么使用它们。元类的主要用途之一是构建API。Django(用python实现的web框架)的ORM就是一个例子。首先用Django定义如下Model:classPerson(models.Model):name=models.CharField(max_length=30)age=models.IntegerField()然后执行如下代码:guy=Person.objects.get(name='bob')printguy.age#resultis35这里打印的输出不是IntegerField,而是一个int,是从数据库中获取的。这是因为models.Model使用__metaclass__来实现复杂的数据库查询。但对你来说,这只是一个简单的API,你不需要关心背后的复杂工作。结论回顾一下,我们知道类是可以创建对象实例的对象,也是元类的对象实例(因为元类创建了它们)。在python中,一切都是对象。它们要么是类的实例,要么是元类的实例,类型除外。type是它自己的元类。至于如何实现,总之用纯python语言是不可能实现的,需要在实现层面做一些小技巧。元类使用起来比较复杂,如果你需要修改非常简单的类,你可能不会使用它们。您可以选择两种技术:MonkeypatchMonkeypatchclassdecorator