一、Python中的类
首先在这里讨论的python类,都是基于新式类进行讨论。
在python中,一切皆为对象。
在理解元类之前我们先来重新理解一下python中的类。
class Joker:
pass
当Python在执行带class语句的时候,会初始化一个类对象放在内存里面。例如这里会初始化一个Joker对象,这个类对象自身拥有创建实例对象的能力。
为了方便后续理解,我们可以尝试一下在新式类中的关键字type。
class Joker:
pass
print(type('123'))
print(type(123))
print(type(Joker()))
>>> <type 'str'>
>>> <type 'int'>
>>> <class '__main__.Joker'>
我们可以看到,type可以查看一个对象的类型,也能够查看创建这个实例对象的类。
但是下面的方法你可能没有见过,type同样可以用来动态创建一个类。
type(类名,父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
这个怎么用呢,我们可以用这个方法创建一个类:
print(type('Joker', (), {}))
>>> <class '__main__.Joker'>
同样我们可以实例化这个类对象:
print(type('Joker', (), {})())
>>> <__main__.joker object at xxxxxxxxx>
可以看到,这里就是一个joker的实例对象。
同样的这个方法还可以初始化创建类的父类,同时也可以初始化类属性:
class Done:
pass
dn = type('Joker', (Done, ), {'sleep': 'zzz'})
print(dn().sleep)
print(dn.__dict__)
print(dn.__bases__)
print(dn().__class__)
print(dn().__class__.__class__)
>>> zzz
>>> {'__module': '__main__', 'sleep': 'zzz', '__doc__': None}
>>> (<class '__main__.Done'>, )
>>> <class '__main__.Joker'>
>>> <type 'type'>
下面我们先来了解几个魔法方法:
1. 方法__class__用于查看对象是由谁创建的,类对象也是对象。类是元类的实例,而实例对象则是类的实例。
2. 方法__bases__用于得到一个对象的父类是谁,特别注意一下__base__返回单个父类,__bases__以元组的形式返回所有父类。
下面我来解释一下上面代码的运行结果:
1. 使用type创建一个类赋值给dn,type接收的三个参数的意思分别为(类的名称,类的父类(可以为空),类的属性(字典形式))。
2. 初始化类的实例,然后尝试去获得类属性的sleep属性。
3. 获取dn这个类的所有属性(字典形式)。
4. 获取dn这个类的父类集合。
5. 获取创建dn()这个实例对象的类。
6. 获取创建dn()这个实例对象的类的类(有点绕,即获取实例化Joker这个类的类)。
二、什么是元类以及简单的运用
介绍完上面的内容后我们开始进入主要环节:
那到底什么是元类呢?
通俗的说,元类就是创建类的类,是不是还是有点抽象?
不要慌,听我慢慢道来:
Joker = MetaClass()
MyObject = Joker()
上面我们已经介绍了,创建一个类可以直接这样搞:
Joker = type('Joker', (), {})
那为什么可以这样来搞呢?
type实际上是一个元类,用它可以去创建类。
也可以说元类实际上就是一个创建类的工厂。
那么类里面的__metaclass__属性,相信很多小朋友对这个很好奇,使用了它实际上就意味着会用__metaclass__指定的元类来创建类了。
class Joker(Done):
pass
当我么在创建上面的类的时候,Python做了如下的操作:
首先,Python会先去Joker里面找是否有__metaclass__这个属性,如果有,Python会在内存中通过__metaclass__创建一个名字为Joker的类对象,也就是Joker;如果没有找到__metaclass__,它会继续在自己的父类Done中寻找__metaclass__属性,并且尝试以__metaclass__指定的方法创建一个Joker类对象。
如果Python在任何一个父类中都没有找到__metaclass__属性,它会去模块中搜寻是否有__metaclass__属性。如果还是找不到,Python会是哦用默认的type来创建Joker。
那么问题来了,__metaclass__属性中设置的是什么呢?
答案是可以创建一个类的东西:type或者任何用到type或子类化type的东西都行。
三、自定义元类
自定义元类的目的,就是拦截类的创建,然后修改一些特性,然后返回该类。
是不是感觉和某种东西很像?没错,它和装饰器很像,为特定的对象附加额外的功能,或者修改某些功能,最后被返回。
其实除了上面谈到的指定一个__metaclass__,我们给它赋值的不一定是一个正式的类,函数也可以。
要创建一个使所有模块级别都是用这个元类创建的话,我们只需要在模块级别设定__metaclass__就可以了。
下面我们来写个例子,将创建的类里面的所有属性名都改为大写。
def upper_attr(class_name, class_parents, class_attr):
'''
返回一个对象,将它的属性都改为大写
:param class_name: 类的名称
: param class_parents: 类的父类tuple
: param class_attr: 类的参数dict
: return: 类
'''
# 使用一个generator来存储类的参数
attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__'))
# 将属性名全部改为大写并转化为dict
uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
return type(class_name, class_parents, uppercase_attrs)
# 设置全局变量
__metaclass__ = upper_attr
# 创建类
upa = upper_attr('Joker', (), {'bar': 0})
print(hasattr(upa, 'bar'))
print(hasattr(upa, 'BAR'))
print(upa.BAR)
>>> False
>>> True
>>> 0
我们可以看到在上面的代码中,实现了一个元类(metaclass),然后指定了模块使用这个元类来创建类,所以当我下面使用type进行创建的时候,可以发现小写的bar参数被替换成了大写的BAR参数,并且在最后可以调用这个被修改后的参数。
上面我们使用函数作为元类传递给类,下面我们使用一个正式类来作为元类传递给__metaclass__
class UpperAttrMetaClass(type):
# 注意它的第一个参数为mcs,这个和类方法用cls、实例方法用self是同一个道理
def __new__(mcs, class_name, class_parents, class_attr):
attrs = ((name, value) for name, value in class_attr if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, class_parents, uppercase_attr)
class Joker:
__metaclass__ = UpperAttrMetaClass
bar = 12
money = 'unlimited'
print(Joker.BAR)
print(Joker.MONEY)
>>> 12
>>> unlimited
总结
这些都是我在搜集了诸多资料之后自行整理的一些笔记,备忘的同时希望也能分享给大家,这个知识点大家没有必要去死记硬背,在需要的时候可以回过头来复习一下,平时的话很少有用到的地方,但是它能让你了解在Python当中类创建的这么一个过程,加深大家对Python的理解。
Reference:
http://blog.jobbole.com/21351/ 深刻理解Python中的元类
http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python What is metaclass in Python