Python中的类和元类(metaclass)以及黑魔法(__metaclass__)

一、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

猜你喜欢

转载自blog.csdn.net/june_young_fan/article/details/79684978