(这里是本章用到的 GitHub 地址)万物皆对象 —— Python
本章所介绍的元类(Meta Class)和之前介绍过的装饰器(Decorator)都是上面这句话的具现,其中装饰器告诉过我们“函数亦对象”,元类则会告诉我们“类亦对象”
Meta Class 是传说中的黑魔法、黑魔法中的战斗机(……)。我其实对它也只一知半解,所以以下说的内容可能仅展现了它神奇功用的冰山一角。不过作为一个入门教程来说的话、可能会刚刚好也说不定(其实只是在为自己的弱小找借口)(喂)
所谓的“类亦对象”和“函数亦对象”的思想类似:它意味着类可以被赋值给变量、通过变量也能创建该类的实例。举个栗子:
class Class:
def __init__(self):
self.x = 1
one = Class
print(one().x)
Out[1]:
1
正如装饰器返回的是一个函数,我们可以认为元类返回的是一个类。 也正如我在讲装饰器里说过的,装饰器的核心思想,就是装饰函数这个对象、让函数自身代码不变的情况下、增添一些具有普适性的功能。在我看来,元类的核心思想,就是捣鼓类这个对象、使你能对其有着最高程度的控制权。
注意:这绝不一定是个准确的理解!正如 Python界的领袖 Tim Peters 说过:元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。
我的理解仅仅来自于我对元类的应用,它很有可能是非常片面的。不过,由于我的目的是为了让大家知道元类的一种可能是最简单的使用姿势、使大家不至于看到代码里面的 metaclass 就怕,所以还请观众老爷们允许我这个半吊子继续用这个理解讲下去(如果有观众老爷有更深更好的理解、欢迎在评论区里面教我、我会把它们贴在这里的 ( σ'ω')σ)
那么什么叫做最高程度的控制权呢?一个比较简单的栗子就是实现如下需求:定义一个“人”(Person)类,它有三个方法:吃饭、睡觉、续几秒(咦)
定义 Person 的三个子类“小张”(Zhang)、“小王”(Wang)、“小江”(Jiang)
定义“人”的子类“小红”(Hong), 要求他:吃饭像小张一样快
睡觉像小王一样香
续秒像小江一样熟练(喂)
你会怎么去实现呢?如果再要求你把上面三个要求换一换顺序呢?
也许 Python 有许多其它的解决方案、但(我所知道的)最简单的方法、就是使用元类了
幸运的是,虽然元类的思想可能很深,但就这个简单的问题而言、即使我不进行任何说明、相信聪明的观众老爷们也能读懂下面这几块代码
先定义 Person 类:
class Person:
def __init__(self):
self.ability = 1
def eat(self):
print("Eat: ", self.ability)
def sleep(self):
print("Sleep: ", self.ability)
def save_life(self):
print("+ ", self.ability, " s")
再定义三个子类:
class Wang(Person):
def eat(self):
print("Eat: ", self.ability * 2)
class Zhang(Person):
def sleep(self):
print("Sleep: ", self.ability * 2)
class Jiang(Person):
def save_life(self):
print("+ inf s")
然后是最关键的、定义元类(Meta Class):
class Mixture(type):
def __new__(mcs, *args, **kwargs):
name, bases, attr = args[:3]
person1, person2, person3 = bases
def eat(self):
person1.eat(self)
def sleep(self):
person2.sleep(self)
def save_life(self):
person3.save_life(self)
attr["eat"] = eat
attr["sleep"] = sleep
attr["save_life"] = save_life
return type(name, bases, attr)
Done!可能会有观众老爷发现其中有三行代码显得“特别傻”——没错,确实可以用更具有普适性的三行代码来代替我们上面倒数第二到第四行的代码:
class Mixture(type):
def __new__(mcs, *args, **kwargs):
name, bases, attr = args[:3]
person1, person2, person3 = bases
def eat(self):
person1.eat(self)
def sleep(self):
person2.sleep(self)
def save_life(self):
person3.save_life(self)
for key, value in locals().items():
if str(value).find("function") >= 0:
attr[key] = value
return type(name, bases, attr)
抛开所有技术细节而只谈应用的话、其实上面这个栗子可能已经相当足够了。接下来就让我们测试一下这个 Mixture元类吧。先来定义一个小的测试函数,它依次调用 Person 实例吃饭、睡觉、续几秒这三个动作:
def test(person):
person.eat()
person.sleep()
person.save_life()
然后进行两组测试:
class Hong(Wang, Zhang, Jiang, metaclass=Mixture):
pass
test(Hong())
Out[2]:
Eat: 2
Sleep: 2
+ inf s
class Hong(Zhang, Wang, Jiang, metaclass=Mixture):
pass
test(Hong())
Out[3]:
Eat: 1
Sleep: 1
+ inf s
Done!可以看到、我们确实获得了类的高度控制权
可能会有观众老爷想问,如果我直接继承会发生什么事情?就这个栗子而言,如果定义一个类直接继承小王、小张、小江的话,无论按什么顺序继承、结果都会是一样的(猜猜这个结果会是什么? ( σ'ω')σ)
值得一提的是,可以看到我们定义的元类继承自 type、这是因为 Python 自带的元类就是 type
其实即使仅仅基于上述栗子的思想、就已经可以捣鼓出许多有意思的应用了。在我自己实现的神经网络中、我就用了这个思想来把普通 NN 里面的附加层(Dropout、Normalize)扩展成了 CNN 里面的附加层,感兴趣的观众老爷们可以看看这里面的 ConvSubMeta 类
我没有讲太多原理层面的东西,一方面是因为我觉得知道怎么用就好、另一方面是因为我怕乱说话遭报应(……)
希望观众老爷们能够喜欢~