Python 元类

元类简介

  • 元类(metaclass),元类是对类的概念的一种抽象定义,type 是创建类的默认元类
  • 创建类的时候,先调用元类的 __new__ 方法,再调用元类的 __init__ 方法,如果元类没有相应的方法,调用 type 的同名方法
  • 创建类的实例的时候,先调用类的 __new__ 方法,再调用类的 __init__ 方法,如果类没有相应的方法,调用 object 的同名方法
  • 当一个类继承自 type 时,这也就是一个元类,创建元类其实就是重写 type 中的各种方法的过程
1
2
3
4
5
6
7
8
9
10
11
def get_name(self):
print(self.name)

def init(self, name):
self.name = name

# 动态的创建类
User = type('User', (object, ), {'__init__': init, 'get_name': get_name})

user = User('王大锤')
user.get_name()

元类 __new__

在定义元类的 __new__ 方法时,通常需要传递以下几个参数

  • metacls - 同 self ,代表元类自身
  • cls_name - 要创建的类的类名
  • super_cls_tuple - 新建的类需要继承的父类的元祖
  • args_dict - 是一个字典,里面包含了类的方法和类属性,如 __module__ \ __doc__ 等, 类的属性也会添加进来

通过自定义元类的 __new__ 方法,可以实现以下特性

  • 默认给类添加属性或方法
  • 强制要求子类实现特定的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 在 list 的基础上创建一个类来使其支持 add 方法
class ListMeta(type):
def __new__(metacls, cls_name, super_cls_tuple, args_dict):
args_dict['add'] = lambda self, value: self.append(value)
return super().__new__(metacls, cls_name, super_cls_tuple, args_dict)

class List(list, metaclass=ListMeta):
pass

if __name__ == '__main__':
my_list = List()
my_list.append(1)
my_list.add(2)
print(my_list) # [1, 2]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 强制要求类实现 add 方法
class ListMeta(type):
def __new__(cls_meta, cls_name, cls_super, args_dict):
if 'add' not in args_dict:
raise TypeError("Class must have add function")
return super().__new__(cls_meta, cls_name, cls_super, args_dict)


class List(list, metaclass=ListMeta):
def add(self, item):
self.append(item)


if __name__ == '__main__':
lst = List()
lst.add(123)

元类 __init__

在定义元类的 __init__ 方法时,通常需要以下参数

  • cls - 进行创建的类本身(不是元类,是新建的类)
  • name - 新建的类的类名
  • bases - 新建的类所继承的类
  • namespace - 新建的类的属性和方法

当调用 __init__ 函数时,类已经被创建了,所以用该方法通常是进行对类的属性进行增删

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ListMeta(type):

def __new__(metacls, cls_name, super_cls_tuple, args_dict):
return super().__new__(metacls, cls_name, super_cls_tuple, args_dict)

def __init__(cls, name, bases, namespace):
if not hasattr(cls, "length"):
cls.length = lambda cls: len(cls)

class List(list, metaclass=ListMeta):
def add(self, item):
self.append(item)

lst = List([1, 2])
lst.add(123)
print(lst.length()) # 3
print(lst) # [1, 2, 123]

元类 __call__

元类中的 __call__ 函数是在创建的类在尝试进行实例化的时候被调用的,所以可以通过该函数来对类进行实例化时做限制,例如

  • 禁用实例化功能
  • 实现单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 禁用实例化功能
class NoInstanceMeta(type):
def __call__(cls, *args, **kwargs):
raise TypeError("Faild to Instance")

class NoInstance(metaclass=NoInstanceMeta):
name = 'stolen'

@classmethod
def hello(cls):
print(f'Hello, {cls.name}')

print(NoInstance.name) # stolen
NoInstance.hello() # Hello, stolen
no_instance = NoInstance() # Raise TypeError
1
2
3
4
5
6
7
8
9
10
# 实现单例模式
class SingletonMeta(type):

def __init__(cls, *args, **kwargs):
cls.__instance = None

def __call__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance