Python 元类的学习 适用的场景

概要

除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

metaclass是Python面向对象里最难理解,也是最难使用的魔术代码。

1.简单的例子(add)

class ListMetaclass(type):
  def __new__(cls,name,bases,attrs):
    attrs['add'] = lambda self,value : self.append(value)
    return type.__new__(cls, name, bases, attrs)
#有了ListMetaclass,我们在定义类的时候还要指示使用ListMetaclass来定制类,传入关键字参数metaclass:
class MyList(list, metaclass=ListMetaclass):
    pass

new()方法接收到的参数依次是:
1.当前准备创建的类的对象;
2.类的名字;
3.类继承的父类集合;
4.类的方法集合。

测试一下MyList是否可以调用add()方法:

>>> L = MyList()
>>> L.add(1)
>> L
[1]

2.ORM例子(“Object Relational Mapping”,即对象-关系映射)

编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

(1)创建一个实例:

u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')

(2)保存到数据库:

u.save()

(3)代码示例

class Field(object): #定义一个类
  def __init__(self,name,column_type):
    self.name = name #数据库表的「列名」(如 id、username)
    self.column_type = column_type #数据库列的「数据类型」(如 varchar(100)、bigint)
  def __str__(self):
    return '<%s:%s>' % (self.__class__.__name__,self.name) #返回类名和列名

class StringField(Field): #继承 Field 类
  def __init__(self,name):
    super(StringField,self).__init__(name,'varchar(100)')

class IntegerField(Field): #继承 Field 类
  def __init__(self,name):
    super(IntegerField,self).__init__(name,'bigint')
class ModelMetaclass(type):
    # cls:元类本身(ModelMetaclass)
    # name:要创建的类的名字(如 User、Blog)
    # bases:要创建的类的父类列表(如 [Model])
    # attrs:要创建的类的属性字典(如 {'id': IntegerField('id'), 'name': StringField('name')})
    def __new__(cls, name, bases, attrs):
        # 1. 跳过基类「Model」的处理(只处理 Model 的子类)
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        
        # 2. 打印日志,提示找到一个数据库表对应的类(方便调试)
        print('Found model: %s' % name)
        
        # 3. 收集类中所有「Field 类型的属性」,构建字段映射字典
        mappings = dict()
        for k, v in attrs.items():
            # 判断属性值是否是 Field 的实例(包括 StringField、IntegerField 子类)
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v  # 保存「类属性名 → 字段实例」的映射(如 'id' → IntegerField('id'))
        
        # 4. 从类属性中删除已收集的 Field 实例(避免属性冲突,后续通过 __mappings__ 访问)
        for k in mappings.keys():
            attrs.pop(k)
        
        # 5. 为类添加核心属性:
        attrs['__mappings__'] = mappings  # 保存字段映射关系(关键!后续 SQL 操作依赖它)
        attrs['__table__'] = name        # 数据库表名 = 类名(简化设计,可自定义修改)
        
        # 6. 调用 type 的 __new__ 方法,创建并返回这个新类
        return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

小结

metaclass是Python中非常具有魔术性的对象,它可以改变类创建时的行为。这种强大的功能使用起来务必小心。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值