理解python的metaclass(元类)

开发 后端
这篇博客是我在stack overflow上看了一个提问回复后写的,例子基本用的都是e-satis本人的例子,语言组织也基本按照翻译来。但我并不是一个翻译者,并不会严格遵守每行每句的翻译;有时候我会将表述换个顺序,省略一些我认为无关紧要的话,以便读者更好理解。

前言

这篇博客是我在stack overflow上看了一个提问回复后写的,例子基本用的都是e-satis本人的例子,语言组织也基本按照翻译来。

但我并不是一个翻译者,并不会严格遵守每行每句的翻译;有时候我会将表述换个顺序,省略一些我认为无关紧要的话,以便读者更好理解。

所以,如果你不喜欢我的语言表述,或者想要看英文原文,可以点击此链接去查看原回复。

类也是对象

在理解metaclass之前,我们先要掌握python中的类(class)是什么。

python中类的概念,是借鉴自smalltalk语言。

在大部分语言中,类指的是"描述如何产生一个对象(object)"的一段代码,这对于python也是如此。

  1. >>> class ObjectCreator(object): 
  2. ...       pass 
  3. ... 
  4. >>> my_object = ObjectCreator() 
  5. >>> print(my_object) 
  6. <__main__.ObjectCreator object at 0x8974f2c>  

但是,在python中,类远不止如此,类同时也是对象。当你遇到关键词class的时候,python就会自动执行产生一个对象。下面的代码段中: 

  1. >>> class ObjectCreator(object): 
  2. ...       pass 
  3. ...  

python在内存中产生了一个名叫做"ObjectCreator"的对象。这个对象(类)自身拥有产生对象(实例instance)的能力。 这就是为什么称呼这东西(后面遇到容易混淆的地方,我们称之为:类对象)也是类的原因。同时,它也是一个对象,因此你可以对它做如下操作:

  • 赋值给变量
  • 复制它
  • 为它增加属性(attribute)
  • 作为参数传值给函数

举例:

  1. >>> print(ObjectCreator) # 你可以打印一个类,因为它同时也是对象 
  2. <class '__main__.ObjectCreator'
  3.  
  4. >>> def echo(o): 
  5. ...     print(o) 
  6. ... 
  7. >>> echo(ObjectCreator) # 作为参数传值给函数 
  8. <class '__main__.ObjectCreator'
  9.  
  10. >>> print(hasattr(ObjectCreator, 'new_attribute')) 
  11. False 
  12. >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class 
  13. >>> print(hasattr(ObjectCreator, 'new_attribute')) 
  14. True 
  15. >>> print(ObjectCreator.new_attribute) 
  16. foo 
  17.  
  18. >>> ObjectCreatorMirror = ObjectCreator # 将类赋值给变量 
  19. >>> print(ObjectCreatorMirror.new_attribute) 
  20. foo 
  21. >>> print(ObjectCreatorMirror()) 
  22. <__main__.ObjectCreator object at 0x8997b4c>  

动态创建类

既然类也是对象,那么我们就可以在运行的时候创建它,跟创建对象一样自然。

首先,我们使用class关键字定义一个产生类的函数: 

  1. >>> def choose_class(name): 
  2. ...     if name == 'foo'
  3. ...         class Foo(object): 
  4. ...             pass 
  5. ...         return Foo # return the class, not an instance 
  6. ...     else
  7. ...         class Bar(object): 
  8. ...             pass 
  9. ...         return Bar 
  10. ... 
  11. >>> MyClass = choose_class('foo'
  12. >>> print(MyClass) # the function returns a class, not an instance 
  13. <class '__main__.Foo'
  14. >>> print(MyClass()) # you can create an object from this class 
  15. <__main__.Foo object at 0x89c6d4c>  

这很容易理解吧。但是,这并不那么动态啊。我们还是需要自己来写这个类的代码。

既然类也是对象,那就应该有用来产生它的东西。这东西就是type。

先来说说你所认识的type。这个古老而好用的函数,可以让我们知道一个对象的类型是什么。 

  1. >>> print(type(1)) 
  2. <type 'int'
  3. >>> print(type("1")) 
  4. <type 'str'
  5. >>> print(type(ObjectCreator)) 
  6. <type 'type'
  7. >>> print(type(ObjectCreator())) 
  8. <class '__main__.ObjectCreator' 

实际上,type还有一个完全不同的功能,它可以在运行时产生类。type可以传入一些参数,然后返回一个类。(好吧,必须承认,根据不同的传入参数,一个相同的函数type居然会有两个完全不同的作用,这很愚蠢。不过python这样做是为了保持向后兼容性。)

下面举例type创建类的用法。首先,对于类一般是这么定义的:

  1. >>> class MyShinyClass(object): 
  2. ...       pass  

在下面,MyShinyClass也可以这样子被创建出来,并且跟上面的创建方法有一样的表现:

  1. >>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object 
  2. >>> print(MyShinyClass) 
  3. <class '__main__.MyShinyClass'
  4. >>> print(MyShinyClass()) # create an instance with the class 
  5. <__main__.MyShinyClass object at 0x8997cec>  

type创建类需要传入三个参数,分别为:

  • 类的名字
  • 一组"类的父类"的元组(tuple) (这个会实现继承,也可以为空)
  • 字典 (类的属性名与值,key-value的形式,不传相当于为空,如一般写法中的pass).

下面来点复杂的,来更好的理解type传入的三个参数:

  1. class Foo(object): 
  2.     bar = True 
  3.  
  4.     def echo_bar(self): 
  5.         print(self.bar)  

等价于:

  1. def echo_bar(self): 
  2.     print(self.bar) 
  3.  
  4. Foo = type('Foo', (), {'bar':True'echo_bar': echo_bar})  

想要看点有继承关系的类的实现,来:

  1. class FooChild(Foo): 
  2.     pass 

 等价于:

  1. FooChild = type('FooChild', (Foo, ), {}) 

回顾一下我们学到哪了: 在python中,类就是对象,并且你可以在运行的时候动态创建类.

那到底什么是metaclass(元类)

metaclass 就是创建类的那家伙。(事实上,type就是一个metaclass)

我们知道,我们定义了class就是为了能够创建object的,没错吧?

我们也学习了,python中类也是对象。

那么,metaclass就是用来创造“类对象”的类.它是“类对象”的“类”。

可以这样子来理解:

  

  1. MyClass = MetaClass() 
  2. MyObject = MyClass()  

也可以用我们上面学到的type来表示:

  1. MyClass = type('MyClass', (), {}) 

说白了,函数type就是一个特殊的metaclass.

python在背后使用type创造了所有的类。type是所有类的metaclass.

我们可以使用__class__属性来验证这个说法.

在python中,一切皆为对象:整数、字符串、函数、类.所有这些对象,都是通过类来创造的.

  1. >>> age = 35 
  2. >>> age.__class__ 
  3. <type 'int'
  4.  
  5. >>> name = 'bob' 
  6. >>> name.__class__ 
  7. <type 'str'
  8.  
  9. >>> def foo(): pass 
  10. >>> foo.__class__ 
  11. <type 'function'
  12.  
  13. >>> class Bar(object): pass 
  14. >>> b = Bar() 
  15. >>> b.__class__ 
  16. <class '__main__.Bar' 

那么,__class__的__class__又是什么呢? 

  1. >>> age.__class__.__class__ 
  2. <type 'type'
  3. >>> name.__class__.__class__ 
  4. <type 'type'
  5. >>> foo.__class__.__class__ 
  6. <type 'type'
  7. >>> b.__class__.__class__ 
  8. <type 'type' 

metaclass就是创造类对象的工具.如果你喜欢,你也可以称之为"类的工厂".

type是python內置的metaclass。不过,你也可以编写自己的metaclass.

__metaclass__ 属性

我们可以在一个类中加入 __metaclass__ 属性.

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

当你这么做了,python就会使用metaclass来创造类:Foo。

注意啦,这里有些技巧的。

当你写下class Foo(object)的时候,类对象Foo还没有在内存中生成。

python会在类定义中寻找__metaclass__ 。如果找到了,python就会使用这个__metaclass__ 来创造类对象: Foo。如果没找到,python就使用type来创造Foo。

请把下面的几段话重复几遍:

当你写如下代码的时候:

  1. class Foo(Bar): 
  2.  
  3. pass  

python做了以下事情:

Foo中有__metaclass__这个属性吗?

如果有,python会在内存中通过__metaclass__创建一个名字为Foo的类对象。

如果python没有在Foo中找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__,并尝试做和前面同样的操作。

如果python由下往上遍历父类也都没有找不到__metaclass__,它就会在模块(module)中去寻找__metaclass__,并尝试做同样的操作。

如果还是没有找不到__metaclass__, python才会用内置的type(这也是一个metaclass)来创建这个类对象。

现在问题来了,我们要怎么用代码来实现__metaclass__呢? 写一些可以用来产生类(class)的东西就行。

那什么可以产生类?无疑就是type,或者type的任何子类,或者任何使用到type的东西都行.

自定义metaclass

使用metaclass的主要目的,是为了能够在创建类的时候,自动地修改类。

一个很傻的需求,我们决定要将该模块中的所有类的属性,改为大写。

有几种方法可以做到,这里使用__metaclass__来实现.

在模块的层次定义metaclass,模块中的所有类都会使用它来创造类。我们只需要告诉metaclass,将所有的属性转化为大写。

  1. # type也是一个类,我们可以继承它. 
  2. class UpperAttrMetaclass(type): 
  3.     # __new__ 是在__init__之前被调用的特殊方法 
  4.     # __new__是用来创建对象并返回这个对象 
  5.     # 而__init__只是将传入的参数初始化给对象 
  6.     # 实际中,你很少会用到__new__,除非你希望能够控制对象的创建 
  7.     # 在这里,类是我们要创建的对象,我们希望能够自定义它,所以我们改写了__new__ 
  8.     # 如果你希望的话,你也可以在__init__中做些事情 
  9.     # 还有一些高级的用法会涉及到改写__call__,但这里我们就先不这样. 
  10.  
  11.     def __new__(upperattr_metaclass, future_class_name, 
  12.                 future_class_parents, future_class_attr): 
  13.  
  14.         uppercase_attr = {} 
  15.         for name, val in future_class_attr.items(): 
  16.             if not name.startswith('__'): 
  17.                 uppercase_attr[name.upper()] = val 
  18.             else
  19.                 uppercase_attr[name] = val 
  20.         return type(future_class_name, future_class_parents, uppercase_attr)  

这里的方式其实不是OOP(面向对象编程).因为我们直接调用了type,而不是改写父类的__type__方法.

所以我们也可以这样子处理:

  1. class UpperAttrMetaclass(type): 
  2.  
  3.     def __new__(upperattr_metaclass, future_class_name, 
  4.                 future_class_parents, future_class_attr): 
  5.  
  6.         uppercase_attr = {} 
  7.         for name, val in future_class_attr.items(): 
  8.             if not name.startswith('__'): 
  9.                 uppercase_attr[name.upper()] = val 
  10.             else
  11.                 uppercase_attr[name] = val 
  12.         return type.__new__(upperattr_metaclass, future_class_name, 
  13.                             future_class_parents, uppercase_attr)  

这样子看,我们只是复用了 type.__new__方法,这就是我们熟悉的基本的OOP编程,没什么魔法可言.

你可能注意到,__new__方法相比于

  1. type(future_class_name, future_class_parents, future_class_attr) 

多了一个参数: upperattr_metaclass, 请别在意,这没什么特别的: __new__总是将"它要定义的类"作为***个参数。

这就好比是 self 在类的一般方法(method)中一样,也是被作为***个参数传入。

当然啦,这里的名字的确是我起的太长了。就像self一样,所有的参数都有它们传统的名称。因此,在实际的代码中,一个metaclass应该是写成下面样子的:

(我们同时使用常见的super来让代码更清晰)

  1. class UpperAttrMetaclass(type): 
  2.  
  3.     def __new__(cls, clsname, bases, attrs): 
  4.         uppercase_attr = {} 
  5.         for name, val in attrs.items(): 
  6.             if not name.startswith('__'): 
  7.                 uppercase_attr[name.upper()] = val 
  8.             else
  9.                 uppercase_attr[name] = val 
  10.         return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, attrs)  

使用了 metaclass 的代码是比较复杂,但我们使用它的原因并不是为了复杂, 而是因为我们通常会使用 metaclass 去做一些晦涩的事情,比如, 依赖于自省,控制继承等等。

确实,用 metaclass 来搞些“黑魔法”是特别有用的,因而会复杂化代码。

但就metaclass本身而言,它们其实是很简单的:中断类的默认创建、修改类、***返回修改后的类.

到底为什么要使用metaclass

现在我们面临一个问题: 为什么要使用metaclass? 它容易出错且晦涩难懂.

好吧,一般来说,我们根本就用不上它, 99%的用户应该根本不必为此操心。

实际用到metaclass的人,很清楚他们到底需要做什么,根本不用解释为什么要用.

metaclass 的一个主要用途就是构建API。Django(一个python实现的web框架)的ORM 就是一个例子。

用Django先定义了以下Model:

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

然后执行下面代码:

  1. guy = Person.objects.get(name='bob'
  2.  
  3. print guy.age # result is 35  

这里打印的输出并不是IntegerField,而是一个int,int是从数据库中获取的.

这是因为 models.Model 使用 __metaclass__来实现了复杂的数据库查询。但对于你看来,这就是简单的API而已,不用关心背后的复杂工作。

结语

复习一下,我们知道了,类是能够创造对象实例的对象,同时也是metaclass的对象实例(因为metaclass创造了它们).

在python中,一切皆为对象。它们要么是类的实例,要么是metaclass的实例, 除了type。

type是它自身的metaclass。至于是怎么实现的,总之纯python语言是不可能实现的,这需要在实现层面上耍一些小手段才能做到的。

metaclass用起来比较复杂, 如果需要对非常简单的类进行修改, 你可能不会使用它。有以下两个技术可以供你选择:

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2016-09-06 19:32:11

PythonWeb

2011-08-08 09:22:10

Python

2009-12-18 13:34:09

Ruby metacl

2022-05-20 12:40:23

PythonMetaclass

2021-03-22 10:20:04

Python元类代码

2023-12-07 09:07:58

2015-08-06 15:13:49

runtimeIOS开发

2021-11-29 05:53:54

元宇宙虚拟VR

2014-03-12 10:19:54

iOS对象

2024-01-29 16:47:44

函数封装开发

2023-12-16 13:21:00

Python元类ORM

2011-07-15 13:49:30

C++友元函数友元类

2021-09-15 09:12:56

Python元编程元数据

2009-04-27 09:41:01

C#WPFTemplate

2018-09-03 10:35:15

编程语言PythonDataclasses

2021-03-08 10:47:44

Python类方法静态方法

2020-03-02 00:32:08

Python列表for循环

2009-12-25 16:31:38

ADO类

2018-05-28 09:20:10

Python迭代for循环

2022-02-21 07:45:29

面向对象代码依赖倒置
点赞
收藏

51CTO技术栈公众号