Python - 元类

元类是 Python 的黑魔法之一,大多数人都不需要掌握,但是可以用来装逼,所以写一篇元类的博客

类是实例

类是描述如何产生一个对象的代码段,就像下面的代码一样

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def get_info(self):
print('Name {}, Age {}'.format(self.name, self.age))

然而 Python 中的类不止如此,在 Python 中类也是一个对象,是 type 的实例

>>> Person.__class__
type

既然类也是一个对象,我们就可以将它

  • 赋值给变量
  • 当参数传递
  • 复制
  • 添加属性

就像下面的代码一样

def cls_add_method(cls):
def haha():
print('haha')

if not hasattr(cls, 'haha'):
setattr(cls, 'haha', haha)
return cls

@cls_add_method # 等价于 A = cls_add_method(A)
class A:
pass

>>> A.haha()
haha

type 的神奇用法

type 是一个非常古老的函数,我们可以通过它来获取对象的类,但是它的神奇之处在于 可以通过参数来创建并返回一个类

>>> type('A', (), {})
__main__.A

# 上面的代码等价于
class A:
pass

type 接收三个参数,类名继承列表类字典

继承

>>> B = type('B', (), {'a': 'c'})

>>> C = type('C', (B,), {})

>>> C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class 'object'>)

>>> C.a
'c'

添加方法

def b(self):
print('abbaa')

>>> C.b = b

>>> c = C()

>>> c.b()
'abbaa'

元类

其实元类就是 类的类,type 就是一个元类,控制 Python 所有类的创建,无论是 intstrlist 等类都是由 type 控制创建的,它们的元类都是 type

>>> lst = []
>>> lst.__class__
<class 'list'>
>>> lst.__class__.__class__
<class 'type'>

当然,我们也可以自定义元类

其实元类不一定是一个类,只需要是一个 callable 对象即可,下面这个例子,定义了一个将一个类的所有属性变为大写的函数,通过 metaclass 来指定一个自定义的元类

def upperclass(clsname, bases, clsdict):
upper_dict = {}
for key, val in clsdict.items():
if key.startswith('_'):
upper_dict[key] = val
else:
upper_dict[key.upper()] = val
return type(clsname, bases, upper_dict)

测试

class D(metaclass=upperclass):
a = 'c'
b = 'd'

>>> d = D()
>>> d.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'a'
>>> d.A
'c'
>>> d.b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'b'
>>> d.A
'c'

但是毕竟是元类嘛,当然要用类啊,标准的做法是定义一个类,通过 __new__ 方法返回一个新的类,

class UpperClass(type):
def __new__(cls, clsname, bases, clsdict):
upper_dict = {}
for key, val in clsdict.items():
if key.startswith('_'):
upper_dict[key] = val
else:
upper_dict[key.upper()] = val
return super().__new__(cls, clsname, bases, upper_dict)


class D(metaclass=UpperClass):
a = 'c'
d = 'e'


>>> D.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'D' has no attribute 'a'

为什么用类,不用函数

  • 使用 oop, 代码简洁明了
  • 可以使用 __new____init____call__ 干更多的事情
  • 元类可以从元类继承方法,或者使用元类,嗯,很高大上
  • 因为是元类,所以用类

事实上,元类只做这几件事

  • 拦截类的创建
  • 修改类
  • 返回修改后的类

参考资料: