译者前言
刚开始学习Python,一直不理解Python中的memetaclass到底是用来做什么的,直到我在stackoverflow上看到这个回答。感觉第二个回答比较好,闲暇时间将它翻译出来作为备用吧。
类对象
在理解metaclass之前,你需要掌握Python中的类。Python中的类借鉴了Smalltalk语言,并且关于类拥有一些奇特的想法。
在大多数语言中,类仅仅是一小段用来描述如何产生对象的代码,这一点在Python中也成立。
1 | class ObjectCreator(object): |
但是,Python中的类不仅仅如此。Python中的类也是对象。
一旦你使用关键字class,Python立即执行并创建一个对象,就像这样在内存中创建一个名字为ObjectCreator的对象
1 | class ObjectCreator(object): |
这个对象(类)能创建对象(实例),这就是为什么被称作类。
译者注:这里的类也是对象,而生成的实例也是对象,或者说称之为实例更好些,英文原文如下:
This object (the class) is itself capable of creating objects (the instances), and this is why it’s a class.
它是一个对象,因此:
- 你可以将它赋值给一个变量
- 你可以拷贝它
- 你可以给它添加属性
- 你可以个哦他传递函数参数
栗子:
1 | print(ObjectCreator) # 你可以打印一个类,因为它是一个对象 |
动态创建类
由于类是一个对象,你可以在运行时动态创建,就像其他对象那样。首先,你可以使用class在函数中创建一个类:
1 | def choose_class(name): |
但是这样并不是那么动态,因为你仍然必须写整个类。既然类是对象,那么肯定有东西能生成它。
当你使用class关键字,Python会自动的创建对象。但是像Python中大多数的东西一样,它可以通过手动创建。
还记得函数type吗?这个古老又非常好用的函数可以让你知道某个对象的类型。
1 | print(type(1)) |
type还拥有不同的能力,他也可以在运行时创建一个类。type可以接收一个类描述作为参数,并且返回一个类。(我知道,根据你传入参数的不同,同一个函数拥有两种不同的作用是一件很傻的事情。在Python中这个为了能够向后兼容)
type的用法是这样的:
1 | type(name of the class, |
栗子:
1 | class MyShinyClass(object): |
也可以通过这种方法手动创建:
1 | MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类对象 |
你会注意到我们使用”MyShinyClass”作为类名,并且作为变量作为这个类的引用。类和变量是不同的,在这里没有必要将它们复杂化。
type接受一个字典来定义类的属性。所以:
1 | class Foo(object): |
也可以写成:
1 | Foo = type('Foo', (), {'bar':True}) |
可以像正常类一样来使用:
1 | print(Foo) |
当然,你也可以从它继承,因此:
1 | class FooChild(Foo): |
或者这样:译者注:这里要注意type函数的第二个参数要使用单个元素的元组写法。
1 | FooChild = type('FooChild', (Foo,), {}) |
最终你想要给你的类添加方法,你只要有定义一个函数,并将其赋值给字典中的某个属性即可。
1 | def echo_bar(self): |
在动态创建类之后,你可以添加更多的方法,就像向常规的类对象添加方法一样。
1 | def echo_bar_more(self): |
你可以看到,在Python里,类就是对象,你可以在运行时动态的创建类。这就是当你使用关键字class的时候,Python在幕后做的事情,他是通过元类来实现的。
什么是元类 (终于)
元类就是创建类的东西,你定义类是为了创建对象,对吧?同样我们也知道Python中的类就是对象。那么,元类创建类,它们是类的类,你可以将它们当成这样:
1 | MyClass = MetaClass() |
你已经看到type让你这样做:
1 | MyClass = type('MyClass', (), {}) |
这是因为函数type是一个元类,它是Python中所有类的元类。现在你可能会疑惑为什么它是小写,而不是Type?
我想它可能是为了与str保持一致,str是创建字符串对象,int是创建整数对象,type就是创建类对象。你可以检查__class__属性。
在Python中一切都是对象,包括整数、字符串、函数、类。它们都是对象,并且所有的这些都可以从类中创建:
1 | age = 35 |
那么,__class__的__class__属性是什么?
1 | age.__class__.__class__ |
所以说,元类就是创建类对象的东西,你可以叫它“类工厂”。type是Python中的内建元类,当然,你也可以创建自己的元类。
metaclass 属性
当你写一个类的时候,可以添加__metaclass__属性:
1 | class Foo(object): |
如果你这样做了,Python将会使用元类来创建类Foo。当心,这里有一些技巧。当你写下class Foo(object),但是类Foo现在还没有在内存当中创建。Python将会在类中查找__metaclass__定义,如果找到了,将会创建类对象Foo,如果没有找到,它将会使用type来创建类。请反复的读这句话。当你写下以下时:
1 | class Foo(Bar): |
Python会按照以下运行:
Foo中有__metaclass__属性吗?如果有,Python通过使用__metaclass__在内存中创建名字为Foo的类对象(我说的是类对象,请跟上我)。如果Python中找不到__metaclass__,它会在模块层级上找__metaclass__,并尝试同样的操作(但只适用于不继承任何东西的类,基本上是旧式的类)。接着,如果Python中没有找到__metaclass__,它会使用Bar(父类)的元类(这里或许默认是type)来穿件类对象。
这里要小心,__metaclass__属性并不是从父类继承而来的,父类中的元类(Bar.__class__)是的。如果Bar使用type()(并不是type.__new__())来创建Bar的__metaclass__属性,子类将不会继承这种行为。
现在有一个很大的问题,__metaclass__中可以存放什么?答案是:可以创建类的东西。那么什么可以创建类呢?type,或者是任何使用到type或者子类化type的东西都可以。
自定义元类
元类的主要目的是当创建类的时候能自动的改变类。通常你会为API做这样的事情,你希望可以创建符合当前上下文的类。
假想已个很傻的例子,当你决定模块中所有的类的属性都应该是大写的形式。有好几种办法可以做到,其中一种是设置模块级别的__metaclass__。幸运的是,__metaclass__能够自动被调用,他并不需要是一个正式的类(我知道某些被称为’class’的东西并不需要是一个class,可以尝试画图理解下,很有帮助)。所以,我们以一个简单的例子作为开始:
1 | # 元类将自动通过传给 type 的参数作为自己的参数 |
现在让我们使用真正的class来作为元类在做一次:
1 | # ‘type’ 实际上是一个类,就像 'str', 'int' |
但是这不是真正的面向对象(OOP)。我们直接调用type,并且并没有改写父类的__new__方法,可以这样写:
1 | class UpperAttrMetaclass(type): |
你或许已经注意到额外的参数upperattr_metaclass,这并没有什么特别的:__new__总是接收它定义的类作为第一个参数。就像你使用self作为一个普通方法或者类的类方法的第一个参数那样。
当然,为了更加清晰,名字我定义的很长。但是就像self那样,所有的参数都有传统的名称,因此在真正的产品中元类应该是这样:
1 | class UpperAttrMetaclass(type): |
我们可以使用super来是它更加清晰,这回有利于继承(是的,你可以拥有元类,从元类继承,从type继承):
1 | class UpperAttrMetaclass(type): |
就是这样,关于元类真的没什么可以说的了。
使用元类的代码比较复杂,其背后原因并不是元类本身。而是你通常使用元类做一些晦涩难懂的事情,依赖自省、控制继承、__dict__等等。
的确,元类在使用黑魔法的时候非常有用,因此会弄出复杂的东西来,但就元类本身,它们是非常简单的:
- 拦截类的创建
- 修改类
- 返回修改的类
为什么使用元类来代替函数?
因为 __metaclass__可以接收任何调用对象,为什么使用类,因为它看起来比较复杂?这里有好几个原因:
- 意图更加清晰,当你读到
UpperAttrMetaclass(type),你会知道接下来做什么。 - 你可以使用面向对象(OOP),元类可以冲元类中继承二改,重写父类的方法,元类甚至可以使用元类。
- 你可以使你的代码更加有结构化。当你使用元类的时候像上面的例子那样简单。通常非常的复杂。有能力将多个方法归到一个类当中,对你的代码可读性非常有帮助。
- 你可以使用
__new__,__init__,__call__方法,这些都允许你做不同的事情。就算你讲所有的东西都放在__new__当中处理,游戏而还是觉得使用__init__更加舒服。 - 这个东西被称作元类,一定要小心使用。
为什么使用元类?
现在有一个大问题,为什么要使用一些容易出错的功能?好吧,通常你用不到它:
“元类就是深入的魔法,99%的用户应该不要担心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。(那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。)” —— Python界的领袖 Tim Peters
元类最主要的用途是创建API。一个典型的例子是 Django ORM。它允许你这样定义:
1 | class Person(models.Model): |
但是,如果你这样定义:
1 | guy = Person(name='bob', age='35') |
它不会返回IntegerField对象,它会返回一个int,甚至可以从数据库中取出来。这是可能的,因为models.Model中定义了一个__metaclass__,并使用了一些魔法能够将你定义的Person类转变成对数据库的复杂的hook。Django将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
结语
首先,你知道类是一个对象并且能够穿件实例。事实上,类本身也是元类的实例。
1 | class Foo(object): pass |
Python中一切都是对象,要么是类的实例,要么是元类的实例。
除了type,type实际上是它自己的元类。在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。
其次,元类是复杂的。你或许不想通过使用它来对简单的类做修改,你可以改变类使用下面的两种技巧:
- monkey patching
- 类装饰器
99%的时间你需要改变类,你最好使用这些,但是99%的时间你根本不需要修改类。:D