Python类元编程初探

在《流畅的Python》一书中提到:

Classes are first-class object in Python, so a function can be used to create a new class ant any time, without using the class keyword.

在Python中,声明一个类可以有两种方法:

>>> class X:
...     a = 1
...
>>> X = type('X', (object,), dict(a=1))

可见类X也是一个type类型的对象。

Import time和runtime

在import一个模块的时候,可以看出有哪些模块中的语句会执行。比如有模块a.py


A = 'A'

class ClassA:
    print('print ClassA')

def printf():
    print('print printf)

当我们执行import a的时候:

>>> import a
print ClassA
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': <module 'a' from 'D:\\Workspace\\a.py'>}

可以看到,类ClassA中的语句会被执行,可以根据根据代码所在在模块中的层级,将一些在顶级的代码称作top-level code。这类代码会在import time被执行。很显然,类中的代码块也会被执行。为什么模块a.py中的函数printf不会被执行呢?根据文章开头提到的,类也是一种对象。声明一个类,相当于执行type创建一个type对象。所以类声明语句会被执行,需要确定声明的类有哪些属性和方法。

__new__和__init__方法

官方文档给出的说明:

object.__new__(cls[, ...]), called to create a new instance of class cls.
object.__init__(self[, ...]), called after the instance has been created (by new()), but before it is returned to the caller.

new() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.

在实例化一个类的对象的时候,首先会调用__new__来确定会实例化哪个类,然后在调用__init__来初始化具体的对象。一般很少使用这种特性。但是在编写一些框架的时候却很有用,比如编写ORM框架。

metaclass的具体使用

上文提到的,可以用type来创建一个类。当type不能满足需求的时候,就需要自定义type方法了。

在廖雪峰的使用元类一文中,需要实现一个简单的ORM。比如定义了一个模型User,它对应数据库中的users表,有四个字段,分别是id、name、email和password:

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

类属性(id、name、email和password)如何和表中的字段关联?如何只是声明一个这样的类,没有任何作用,解释器会调用type('User', (Model, ), {'id': IntegerField('id')})来创建type对象。很显然默认的type方法不满足需要。所以需要自定义一个type方法:

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s==>%s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        print(cls)
        attrs['__table__'] = attrs['__tablename__']
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        print('ModelMetaclass.__new__')
        return type.__new__(cls, name, bases, attrs)

这样就可以调用ModelMetaclass('User', (Model,), {'id': IntegerField('id')})来创建一个类对象。粘贴复制加魔改廖雪峰博文中的代码,省略部分代码:

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

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

class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s==>%s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        print(cls)
        attrs['__table__'] = name # 假设表名和类名一致
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        print('ModelMetaclass.__new__')
        print(attrs)
        return type.__new__(cls, name, bases, attrs)


class Model(dict):
    
    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.__tablename__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

    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


User = ModelMetaclass('User', (Model, ), {'__tablename__': 'users', 'id': IntegerField('id')})

在上面的代码中,我们使用ModelMetaclass来动态的创建User类,这和廖雪峰博文中在声明类的时候指明metaclass=ModelMetaclass是一样的效果。sqlalchemy中就是使用这种方式来动态的创建类的:

def declarative_base(
    bind=None,
    metadata=None,
    mapper=None,
    cls=object,
    name="Base",
    constructor=_declarative_constructor,
    class_registry=None,
    metaclass=DeclarativeMeta,
):
    lcl_metadata = metadata or MetaData()
    if bind:
        lcl_metadata.bind = bind

    if class_registry is None:
        class_registry = weakref.WeakValueDictionary()

    bases = not isinstance(cls, tuple) and (cls,) or cls
    class_dict = dict(
        _decl_class_registry=class_registry, metadata=lcl_metadata
    )

    if isinstance(cls, type):
        class_dict["__doc__"] = cls.__doc__

    if constructor:
        class_dict["__init__"] = constructor
    if mapper:
        class_dict["__mapper_cls__"] = mapper

    return metaclass(name, bases, class_dict)

猜你喜欢

转载自www.cnblogs.com/mrzysv5/p/10759664.html
今日推荐