Python: 使用元类创建类Python: 使用元类创建类Python: 使用元类创建类Python: 使用元类创建类
  • 首页
  • 博客
  • 书签
  • 文件
  • 分析
  • 登录

Python: 使用元类创建类

发表 admin at 2022年8月12日
类别
  • Python
标签

Python的type函数能够返回对象的类型,先看下面一段代码

class Model(object):
    pass

a = Model()
print(type(a))
# <class '__main__.Model'>

输出的结果是a的类型:“Model”,这个是在意料之中的

不过,Python中的类也是一个对象,来看一下它的类型:

print(type(Model))
# <class 'type'>

Model返回的类型是Type;可以这样理解,因为Model是类,也就是一种类型(type),所以创建一个类就是是创建了一种类型(即type的实例)

上面说的有点绕,简单了说就是:创建一个类就是创建了一个type的实例

所以很自然的一个疑问是,如果类是type的一个对象,那么这个类是如何与type关联起来的?

在定义类的时候,可以给它指定一个元类(metaclass)

class Model(object, metaclass=type):
    pass

print(type(Model))
# <class 'type'>

这里把Model的metaclass设置成了type,输出类型仍是‘type’,似乎没什么作用,接着:

class ModelMeta(type):
    pass

class Model(object, metaclass=ModelMeta):
    pass

print(type(Model))
# <class '__main__.ModelMeta'>

现在先定义一个ModelMeta类,然后将Model的元类设置成ModelMeta,再打印Model的类型,结果变成了‘ModelMeta’类型

到这里可以初步认为,类作为一个对象,是由它的元类所创建,而默认的元类就是type,下面来进一步确认

class ModelMeta(type):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("init a", self)
        print("init a", self.__class__.__name__)

class Model(object, metaclass=ModelMeta):
    pass

# init a <class '__main__.Model'>
# init a ModelMeta

上面的代码除了定义类Model和它的元类ModelMeta,其他任何代码都没有,但是还是打印了两句话:

init a <class '__main__.Model'>
init a ModelMeta

对照代码可以知道这是ModelMeta类的__init__执行的结果,也就是说有一个ModelMeta实例被创建了

因为在程序运行之后,python解释器会将所有的类、函数等都创建好,所以上面通过追踪ModelMeta类的init过程,证明了Model类确实是由它的metaclass,也就是ModelMeta这个类所创建的对象

在搞清楚这个原理后,就可以干很多事情了,例如,在原来的代码上,加上句打印:

m = Model()
print(m)
# <__main__.Model object at 0x0000000003A42828>

上面创建一个Model对象,打印一切正常,再看看下面的代码:

class ModelMeta(type):
    def __call__(self, *args, **kwargs):
        print("hello")

class Model(object, metaclass=ModelMeta):
    pass

m = Model()
print(m)
# hello
# None

可以看到,这一次没有创建成功Model对象,而是返回了一个None

关键在于ModelMeta的魔术方法__call__,因为Model是ModelMeta的一个对象,所以执行

Model()

实际上是就执行了ModelMeta的__call__方法,而该方法里面并没有执行创建对象的步骤,只是打印了一句hello,所以就返回了一个None

现在再修改一下:

    def __call__(self, *args, **kwargs):
        print("hello")
        return super().__call__(*args, **kwargs)

...
m = Model()
print(m)
# hello
# <__main__.Model object at 0x0000000003A55898>

这一次正常创建了Model对象,所以,当有时候有某种需要不希望一个类能够创建对象(比如只希望这个类提供一系列静态方法)时,可以使用这种方法:

class ModelMeta(type):
    def __call__(self, *args, **kwargs):
        raise TypeError("NO")

class Model(object, metaclass=ModelMeta):
    @staticmethod
    def func():
        print("fun")

再比如说单例模式的实现:

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 Model(metaclass=Singleton):
    pass

a = Model()
print(a)
b = Model()
print(b)
# <__main__.Model object at 0x0000000003A44710>
# <__main__.Model object at 0x0000000003A44710>

可以看到a和b是同一个对象,还有一些设计模式,例如工厂模式等等也可以通过元类轻松实现

然后是通过重载元类的__new__方法,来对类进行动态改造:

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        print('name:', name)
        print('bases:', bases)
        for k, v in attrs.items(): print('{}: {}'.format(k, v))
 
class Model(object, metaclass=ModelMetaclass):
    class_attribute_1 = 1
    class_attribute_2 = 'a'
    def __init__(self):
        self.obj_attribute_1 = 5
    
    def func(self):
        pass


# name: Model
# bases: (<class 'object'>,)
# __module__: __main__
# __qualname__: Model
# class_attribute_1: 1
# class_attribute_2: a
# __init__: <function Model.__init__ at 0x0000000003A36EA0>
# func: <function Model.func at 0x0000000003A36F28>

在创建Model类的时候,因为它的元类是ModelMetaclass,所以会创建一个ModelMetaclass的实例,自然而然的会调用到它的__new__方法,从输出中可以看到,参数name是要创建的类的名称,bases是这个类的基类,而attrs是个字典,包括所有这个类的属性(是类的属性,不是对象的属性)

这里的参数和内置的type函数的参数是一致的,如果使用type函数动态创建类型的话也是使用这三个参数:

new_type = type('Hello', (object,), {'say': lambda self: print('hello')})
print(type(new_type))
t = new_type()
t.say()
# <class 'type'>
# hello

上面,使用type动态创建了一个类型(也就是类),并且给了它一个say方法;可以看到,成功创建了一个类,并且调用了这个方法

铺垫都讨论完了,最后贴一段抄来的代码做纪念:

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)
        print('Found model: %s' % name)
        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)
        attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
        attrs['__table__'] = name  # 假设表名和类名一致
        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):
        '''
        重载__getattr__, __setattr__方法使子类可以像正常的类使用
        '''
        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))


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


# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# # 保存到数据库:
u.save()

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

类别

  • Cat
  • Python
  • MySQL
  • Django
  • Html/CSS
  • JavaScript
  • Vue
  • RegExp
  • php
  • Practice
  • Virtualization
  • Linux
  • Windows
  • Android
  • NAS
  • Software
  • Hardware
  • Network
  • Router
  • Office
  • WordPress
  • SEO
  • English
  • Games
  • Recipes
  • living
  • Memorandum
  • Essays
  • 未分类

归档

©2015-2023 艾丽卡 Blog support@alaica.com
      ajax-loader