Python面向对象进阶-元类
元类是什么
在面向对象(OOP)编程中,我们可以用不同的类来描述不同的实体及操作,可以通过父类来设计一些“默认”操作,也可以用MixIn类来组合扩展一些额外操作,也可以用抽象类及抽象方法来描述要实现的接口,面向接口编程。
大部分情况下我们并不需要用到元类。
元类是一种type
(type的子类),是一种自定义类型,可以定制类的调用、对象创建、初始化、销毁等各种操作。
元类的使用场景
多数情况下元类用来对普通类来加以限制和规范。使用元类(自定义类型)可以在类的创建(__new__
)、初始化()比如限制类必须包含特定属性和实现特定方法、限制类只能有一个实例对象。
典型使用场景如下:
- 不允许类实例化
- 单例模式:每个类只允许创建一个对象
- 根据属性缓存对象:当某一属性相同时返回同一对象
- 属性限制:类中必须包含某些属性或实现某些方法
- ORM框架:用类及类属性来描述数据库表并转为数据库操作
元类使用示例
不允许类实例化
在某些情况下,假设我需要限制一些类不允许创建对象(只允许使用类名操作),可以使用元类加以限制,代码如下。
class NoInstances(type): # 定义元类-继承type
def __call__(self, *args, **kwargs): # 控制类调用(实例化)过程
"""类调用"""
raise TypeError("不允许实例化")
class User(metaclass=NoInstances): # 声明使用元类(该类型)
pass
user = User() # ()即调用类的__call__操作,这里会抛出异常,因此无法直接实例化创建对象
单例模式
在某些情况下仅允许类创建一个实例对象,也可以使用元类进行限制,代码如下:
class Singleton(type): # 单例类型-定制的元类
def __init__(self, *args, **kwargs):
self.__instance = None # 添加一个私有属性,用于保存唯一的实例对象
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs): # 控制类调用
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs) # 不存在则创建
return self.__instance
else:
return self.__instance # 否则返回已创建的
class User(metaclass=Singleton):
def __init__(self):
print('创建用户')
user1 = User() # 创建对象
user2 = User() # 创建对象
print(user1 is user2) # 返回True,两者是同一对象
根据属性缓存对象
这个是单例模式的扩展,针对特定属性,完全相同的属性组合创建同一对象。
import weakref
class Cached(type):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__cache = weakref.WeakValueDictionary() # 添加一个缓存字典
def __call__(self, *args):
if args in self.__cache: # 通过 参数组合查询缓存字典中有没有对应的对象
return self.__cache[args]
else:
obj = super().__call__(*args) # 创建对象
self.__cache[args] = obj # 根据参数组合(元祖类型)到缓存字典
return obj
class User(metaclass=Cached):
def __init__(self, name):
print('创建用户({!r})'.format(name))
self.name = name
a = User('张三')
b = User('李四')
c = User('张三')
print(a is b) # False 名字不同,不是同一对象
print(a is c) # True 名字相同,是同一对象
限制类必须包含特定属性
class TestCaseType(type):
def __new__(cls, name, bases, attrs):
print('name', name)
print('bases', bases)
print('attrs', attrs)
if {'priority', 'timeout', 'owner', 'status', 'run_test'} - set(attrs.keys()):
raise TypeError('测试用例类必须包含priority、status、owner、timeout属性并实现run_test方法')
return super().__new__(cls, name, bases, attrs)
class TestA(metaclass=TestCaseType):
# priority = 'P1'
timeout = 10
owner = 'kevin'
status = 'ready'
def run_test(self):
pass
a = TestA() # 这里注释了用例类的priority属性,实例化会报错
ORM框架
ORM(Object-Relational Mapping)是一种将对象和关系数据库之间的映射的技术,它可以让我们使用面向对象的方式来操作数据库。
Django中的ORM模型、以及SQLAlchemy都是基于元类实现的,将数据库操作映射为 类声明和对象操作,下面是一个基于元类的,简单的ORM框架的实现。
class ModelMeta(type): # 元类
def __new__(cls, name, bases, attrs):
if name == 'Model':
return super().__new__(cls, name, bases, attrs)
table_name = attrs.get('__table__', name.lower()) # 如果类中包含table_name属性,则以该属性作为表明
mappings = {}
fields = []
primary_key = None
for k, v in attrs.items():
if isinstance(v, Field):
mappings[k] = v
if v.primary_key:
if primary_key:
raise RuntimeError('Duplicate primary key for field: {}'.format(k)) # 只允许一个Field声明为主键
primary_key = k
else:
fields.append(k)
if not primary_key:
raise RuntimeError('Primary key not found for table: {}'.format(table_name)) # 不允许没有主键
for k in mappings.keys():
attrs.pop(k)
attrs['__table__'] = table_name
attrs['__mappings__'] = mappings
attrs['__fields__'] = fields
attrs['__primary_key__'] = primary_key
return super().__new__(cls, name, bases, attrs)
class Model(metaclass=ModelMeta): # 数据模型-对应一张数据库表
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def save(self): # 对象保存方法-对应数据库表插入数据
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
if v.primary_key:
continue
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'INSERT INTO {} ({}) VALUES ({})'.format(self.__table__, ','.join(fields), ','.join(params))
print('SQL:', sql)
print('ARGS:', args)
class Field: # 数据库字段
def __init__(self, name, column_type, primary_key=False):
self.name = name
self.column_type = column_type
self.primary_key = primary_key
def __str__(self):
return '<{}:{}>'.format(self.__class__.__name__, self.name)
class StringField(Field): # 字符串类型字典-对应varchar
def __init__(self, name, primary_key=False):
super().__init__(name, 'varchar(100)', primary_key)
class IntegerField(Field): # 整型字典-对应bigint
def __init__(self, name, primary_key=False):
super().__init__(name, 'bigint', primary_key)
在这个示例中,我们定义了一个ModelMeta
元类,它用于创建继承自Model
的类。在这个元类中,我们首先获取类中定义的所有字段,并将它们存储在__mappings__
字典中。同时,我们还获取了主键字段,并将其存储在__primary_key__
属性中。
在Model
类中,我们定义了一个__init__
方法,它用于初始化对象的属性。我们还定义了一个save
方法,它用于将对象保存到数据库中。在这个方法中,我们首先获取所有非主键字段,并将它们存储在fields列表中。然后,我们使用params列表来存储占位符,使用args列表来存储参数。最后,我们使用这些信息来构建SQL语句,并将其打印出来。
在Field
类中,我们定义了一个__str__
方法,它用于打印字段的名称和类型。我们还定义了StringField
和IntegerField
两个子类,它们分别表示字符串类型和整数类型的字段。
使用这个ORM框架,我们可以定义一个继承自Model
的类,并在其中定义字段。例如:
class User(Model):
id = IntegerField('id', primary_key=True)
name = StringField('username')
email = StringField('email')
password = StringField('password')
然后,我们就可以创建一个User对象,并将其保存到数据库中:
user = User(id=1, name='Alice', email='alice@example.com', password='123456')
user.save()
这个示例只是一个简单的ORM框架,实际的ORM框架还需要支持更多的功能,比如查询、更新、删除等操作。但是,通过这个示例,您可以了解到如何使用元类来创建ORM框架。
注册和自动继承父类
class AutoBase(type):
registered_bases = []
def __new__(cls, name, bases, attrs):
bases = tuple(list(bases) + cls.registered_bases)
return super().__new__(cls, name, bases, attrs)
@classmethod
def register_base(cls, register_base):
cls.registered_bases.append(register_base)
def decorator(base_class):
AutoBase.register_base(base_class)
@decorator
class FatherClassA:
pass
class MainClass(metaclass=AutoBase):
pass
print(MainClass.__bases__)