metaclass在Python中是个“逆天”的存在,有人认为它是“阿拉丁神灯”,无所不能;有人认为它是“潘多拉魔盒”,会蛊惑程序员去滥用,释放“恶魔”,然后悲剧就产生了。就连硅谷一线大厂要想使用metaclass都得需要特批。深入理解它的Python开发人员占比不到0.1%。
它会带来好处也容易带来灾难,只有深入了解它,才能使用好它。
一切皆对象
类也是对象
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段,在Python中这一点仍然成立。但是,Python中的类还远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。下面的代码段:
class MyClass(object):
pass
将在内存中创建一个对象,名字就是MyClass。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是你可以对它做如下的操作: 你可以将它赋值给一个变量, 你可以拷贝它, 你可以为它增加属性, 你可以将它作为函数参数进行传递。
在Python中有两种对象:
类型(类)对象:可以被实例化和继承;
非类型(实例)对象:不可以被实例和继承。
耳熟能详的一句话,Python中一切皆为对象:
在Python里,int整形是对象,整数2也是对象,定义的函数、类都是对象,定义的变量也是对象。总之,在Python里能用到的都可以称之为对象。
type和object
明白了Python中一切皆对象之后,再来了解一下Python中对象之间的两种关系。面向对象体系中有两种关系:
父子关系:通常描述为“继承关系的两个类,被继承的那个类是父类,继承的那个类是子类”。
类型实例关系:这种关系存在于两个对象中,其中一个对象(实例)是另一个对象(类型)的具体实现。
Python中万物皆对象,一个Python对象可能拥有两个属性,__class__ 和 __bases__,__class__表示这个对象是谁创建的,__bases__表示这个类的父类。__class__和type()函数效果一样。
代码示例:
class MyClass:
pass
>>> MyClass.__class__
type
>>> MyClass.__bases__
(object,)
>>> int.__class__
type
>>> int.__bases__
(object,)
>>> object.__class__ # object是type的实例,object创建自type
type
>>> object.__bases__ # object没有超类,它本身是所以对象的超类
()
>>> type.__class__ # type创建自本身
type
>>> type.__bases__ # type继承自object,即object是type的超类
(object,)
从上面的代码可以知道:
type为对象的顶点,所有对象都创建自type。
object为类继承的顶点,所有类都继承自object。
这是一个重点!
元类、类、实例
object是所有类的超类,而且type也是继承自object;所有对象创建自type,而且object也是type的实例。
“type是object的类型,同时,object又是type的超类”,那到底是先有object还是先有type呢?这就像“先有鸡还是先有蛋问题”。
object和type是Python中的两个源对象,事实上,它们是互相依赖对方来定义,所以它们不能分开而论。
通过这两个源对象可以繁育出一堆对象:list、tuple、dict等。元类就是这些类的类,这些类是元类的实例。
metaclass
type(“造物的上帝”)
就像str是用来创建字符串对象的类,int是用来创建整数对象的类,而type就是创建类对象的类。
类本身不过是一个名为type类的实例。在 Python的类型世界里,type这个类就是造物的上帝。用户自定义类,只不过是type类的__call__运算符的重载。当我们定义一个类的语句结束时,真正发生的情况,是 Python 调用 type 的__call__运算符。简单来说,当你定义一个类时,写成下面时:
class MyClass:
data = 1
Python 真正执行的是下面这段代码:
class = type(classname, superclasses, attributedict)
这里等号右边的type(classname, superclasses, attributedict),就是type 的__call__运算符重载,它会进一步调用:
type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)
这一切都可以通过代码验证,比如下面这段代码示例:
class MyClass:
data = 1
>>> instance = MyClass()
>>> MyClass, instance
(__main__.MyClass, <__main__.MyClass at 0x1b1b238a688>)
>>> MyClass = type('MyClass', (), {'data': 1})
>>> instance = MyClass()
>>> MyClass, instance
(__main__.MyClass, <__main__.MyClass at 0x1b1b2387708>)
>>> instance.data
1
由此可见,正常的 MyClass 定义,和手工去调用type运算符的结果是一样的。
class type(name, bases, dict)参数详解:
使用1个参数,返回对象的类型。
就像object.__class__。
使用3个参数,返回一个新类型对象。本质上,这是类声明的一种动态形式。
参数name是一个字符串,表示类名称,并记录为__name__属性;
参数bases是一个元组,一个个记下基础类,并记录为__bases__属性;
参数dict是一个字典,包含类本体的命名空间并被赋值到标准字典。并记录为__dict__属性。
举个例子:下面两个声明创建了相同类型的对象:
class X:
a = 1
X = type('X', (object,), dict(a=1))
在Python 3.6发生改变, type的子类不能重写 type.__new__ ,不久将不再使用单参数的样式获取对象的类型。
slots(定位,跟踪)
当在类中定义一个魔术方法的时候,function除了__dict__中的条目之外,在整个类结构中,作为一个描述着这个类的指针一样结束。这个结构对于每一个魔术方法有一个字段。出于一些原因这些字段被称为type slots。
现在,这里有另一个特征,通过__slots__属性执行,一个拥有__slots__的class创造的实例不包含__dict__(这将使用更少的内存)。副作用是实例不能出现未在__slots__中指定的字段:如果你尝试设置一个不存在于__slots__中的字段,那么将会获得一个报错。
这里提及的单独的 slots都是 type slots不是 __slots__。(类里的魔术方法)
class Foobar:
"""
A class that only allows these attributes: "a", "b" or "c"
"""
__slots__ = "a", "b", "c"
>>> foo = Foobar()
>>> foo.a = 1
>>> # foo.x = 2
样例中去掉最后一行注释, foo.x = 2会报错。
metaclass属性
metaclass是type的子类,通过替换type的__call__运算符重载机制,“超越变形”正常的类。其实,理解了以上几点,我们就会明白,正是 Python 的类创建机制,给了metaclass大展身手的机会。
一旦把一个类型MyClass的 metaclass 设置成MyMeta,MyClass就不再由原生的type创建,而是会调用MyMeta 的__call__运算符重载。
class = type(classname, superclasses, attributedict)
# 变为了
class = MyMeta(classname, superclasses, attributedict)
来看个例子:
class MyMeta(type):
def __new__(cls, *args, **kwargs):
print('===>MyMeta.__new__')
print(cls.__name__)
return super().__new__(cls, *args, **kwargs)
def __init__(self, classname, superclasses, attributedict):
super().__init__(classname, superclasses, attributedict)
print('===>MyMeta.__init__')
print(self.__name__)
print(attributedict)
print(self.tag)
def __call__(self, *args, **kwargs):
print('===>MyMeta.__call__')
obj = self.__new__(self, *args, **kwargs)
self.__init__(self, *args, **kwargs)
return obj
class Foo(object, metaclass=MyMeta):
tag = '!Foo'
def __new__(cls, *args, **kwargs):
print('===>Foo.__new__')
return super().__new__(cls)
def __init__(self, name):
print('===>Foo.__init__')
self.name = name
# ----------------------输出----------------------
# ===>MyMeta.__new__
# MyMeta
# ===>MyMeta.__init__
# Foo
# {'__module__': '__main__', '__qualname__': 'Foo', 'tag': '!Foo', '__new__': <function Foo.__new__ at 0x000001B1B2379678>, '__init__': <function Foo.__init__ at 0x000001B1B2379708>, '__classcell__': <cell at 0x000001B1B23880A8: MyMeta object at 0x000001B1B1A509A8>}
# !Foo
>>> print('test start')
>>> foo = Foo('test')
>>> print('test end')
# test start
# ===>MyMeta.__call__
# ===>Foo.__new__
# ===>Foo.__init__
# test end
在创建Foo类的时候,python做了如下操作:
Foo中有metaclass这个属性吗?如果是,Python会在内存中通过metaclass创建一个名字为Foo的类对象。
如果Python没有找到metaclass,它会继续在父类中寻找metaclass属性,并尝试做和前面同样的操作。
如果Python在任何父类中都找不到metaclass,它就会在模块层次中去寻找metaclass,并尝试做同样的操作。
如果还是找不到metaclass,Python就会用内置的type来创建这个类对象。
现在的问题就是,你可以在metaclass中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东西都可以。用类实现可以(比如上面这个例子),用函数实现也可以。但是metaclass必须返回一个类。
def MyMetaFunction(classname, superclasses, attributedict):
attributedict['year'] = 2019
return type(classname, superclasses, attributedict)
class Foo(object, metaclass=MyMetaFunction):
tag = '!Foo'
def __new__(cls, *args, **kwargs):
print('===>Foo.__new__')
return super().__new__(cls)
def __init__(self, name):
print('===>Foo.__init__')
self.name = name
>>> foo = Foo('test')
===>Foo.__new__
===>Foo.__init__
>>> print('name:%s,tag:%s,year:%s' % (foo.name, foo.tag, foo.year))
name:test,tag:!Foo,year:2019
把上面的例子运行完之后就会明白很多了,正常情况下我们在父类中是不能对子类的属性进行操作,但是元类可以。换种方式理解:元类、装饰器、类装饰器都可以归为元编程。
总结
正常情况下我们在父类中是不能对子类的属性进行操作,但是元类可以,这就使得程序代码的维护变得困难。metaclass是 Python 的黑魔法之一,在掌握它之前不要轻易尝试。感觉上Python的规范原则很松,但这也使得Python更灵活,对代码的编写理应由程序员自己负责,自己写的代码还是得自己负责啊。
元类、装饰器、类装饰器都可以归为元编程,它们之间有一些相似点,还是在实际的应用中选择比较,使用合适的工具进行编程。