类也是对象
在理解元类之前,你需要先掌握Python中的类。
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立。即类在定义好了之后,可以用于创建该类的实例。
但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。
下面的代码段(Python3):
|
>>> class Clazz:
… pass
…
>>> print(Clazz)
<class '__main__.Clazz’>
>>> print(hasattr(Clazz, 'attr'))
False
>>> Clazz.attr = “new_attr” #一个类本身可以赋值,也就是类本身是个对象
>>> print(hasattr(Clazz, 'attr'))
True
>>> ClassMirro = Clazz #一个类可以被赋值给其他变量
>>> print(ClassMirro())
<__main__.
Clazz
object at 0x102a90208>
|
Clazz这个类自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类(描述如何生成对象)。
但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:
1) 你可以将它赋值给一个变量
2) 你可以拷贝它
3) 你可以为它增加属性
4) 你可以将它作为函数参数进行传递
动态地创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。
- 你可以在函数中创建类,使用class关键字即可。当你使用class关键字时,Python解释器自动创建这个对象。
|
>>> def choose_class(name):
… if name == 'foo':
… class Foo(object):
… pass
… return Foo # 返回的是类,不是类的实例
… else:
… class Bar(object):
… pass
… return Bar
…
>>> MyClass = choose_class('foo')
>>> print MyClass # 函数返回的是类,不是类的实例
<class '__main__'.Foo>
>>> print MyClass() # 你可以通过这个类创建类实例,也就是对象
<__main__.Foo object at 0x89c6d4c>
|
但这还不够动态,因为你仍然需要是在代码中定义类。
这里只表现出来了一个特性,类是可以传递的和被赋值的。
|
- 利用Type函数创建
由于类也是对象,所以它们必须是通过什么东西来生成的才对。但就和Python中的大多数事情一样,Python仍然提供给你手动生成的方法, 即使用type函数。
type函数的通常用法是给出当前对象的类型,就像这样:
|
>>> print type(1)
<type 'int'>
>>> print type(“I’m a String")
<type 'str'>
>>> print type(Clazz)
<type 'type'>
>>> print type(Clazz())
<class '__main__.Clazz'>
|
type函数能够打印当前对象是什么类型
可以看到 1 是 int型, “I’m a String”则是一个String型的
Clazz是之前定义的类,这里可以看到,
一个类本身是 type 型的。
而 Clazz() 创建了一个实例,理所应当是 Clazz型的。
|
Type还有一种完全不同的能力,即动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。例如
| type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值)) |
|
class Clazz:
pass
等价于
Clazz = type('Clazz', (), {}) # 返回一个类对象
|
|
type 接受一个字典来为类定义属性,因此
clazz Clazz:
attr = “I’m a attr”
def func(self):
pass
等价于
def func(self):
pass
Clazz = type('Clazz', (), {“attr": "I’m a attr", "func": func}) # 返回一个类对象
|
|
继承也可以
class ClazzChild(Clazz):
pass
等价于
ClazzChild = type('ClazzChild', (FooClazz),{})
|
这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。
注:
type参数不同,表现的行为是不同的,这相当于Java的多态。
type(object) -> the object's type
type(name, bases, dict) -> a new type
注2:
type 由于是类,因此它的 type 也是 type。 即type(type)结果是<class 'type'>
元类MetaCalss
元类就是用来创建类的“东西”。元类就是用来创建这些类(同时也就是对象)的,即元类创建了对象,从而可以看出元类本身也是类。这就是MetaClass的命名由来。
上面我们说明了type本身可以创建类,因此type实际上是一个元类。
事实上type就是Python在背后用来创建所有类的元类。
现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。Python中所有的东西都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。我们可以通过 __class__属性来看到。实际上 type(oneObj) 就是打印了 __class__ 属性。
既然如此,那么很显然任何一个__class__的__class__都是type。因为任何一个对象(非类定义)的类为其从属的类,而类的类始终是type。而一个对象(类定义)其从属的类就已经是type了,再套一层还是type。
从上面分析可以知道,事实上,类本身也是实例,当然,它们是元类的实例。默认情况下是type的实例。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的。
修改
__metaclass__属性来自定义元类
默认情况下,编译器使用type作为元类来创建类。但是还可以在写一个类的时候为其添加__metaclass__属性类来指定自定义元类。如果你这么做了,Python就会用__metaclass__来创建类而不是type。
你首先写下class Clazz(object),但是类对象Clazz还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Clazz,如果Python没有找到,它会继续在父类中寻找,如果Python在任何父类中都找不到__metaclass__,它就
会在模块层次中去寻找,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。
现在的问题就是,你可以在__metaclass__中放置些什么代码呢?
答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的过程都可以。
自定义元类的主要目的就是为了当创建类时能够自动地改变类或者判断。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。
例如,你决定在你的模块里所有的类的属性都应该是大写形式(或者其他的一些修改或者判断,例如参数名不能包含某些关键词等等,再比如增加默认属性等等,或者通过配置选择需要的属性等等)。可以通过在模块级别设定__metaclass__来达到这个目的。采用这种方法,由于这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就可以了。
幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。
- 所以,我们这里就先以一个简单的函数作为例子开始
|
- 用一个真正的Class来当做自定义元类。在这种情形下,
一个方法常常将当前的实例作为首个参数,就像平常的方法中的self。__new__方法中有一个额外的参数cls,就是这样的惯例。
|
|
由于__metaclass__可以接受任何可调用的对象, 例如上一个实现中直接让 __metaclass__ 等于函数。
那么有必要像本例一样,使用一个真正的Class来当做自定义元类么?有必要,好处在于:
1:UpperAttrMetaclass(type)直接可以看出来是从type中继承来的,对其基本行为是可以预测的。提高了代码的维护性。
2:可供继承
3:自定义元类通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。
4:你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。
|
使用到自定义元类的代码比较复杂,这背后的原因倒并不是因为自定义元类本身,而是因为你通常会使用自定义元类去做一些晦涩的事情,依赖于自省,控制继承等等。
确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。但就自定义元类本身而言,它们其实是很简单的:
1) 拦截类的创建
2) 修改类
3) 返回修改之后的类
结语
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义
|
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
|
|
guy = Person(name='bob', age='35')
print guy.age
这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。
|
因为models.Model定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。
Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。
元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:
1) Monkey patching
2) class decorators
当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类
参考:
本文深入讲解Python中的元类概念,包括类如何被视为对象、如何使用type函数动态创建类及自定义元类的方法。
345

被折叠的 条评论
为什么被折叠?



