首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python3 metaclass--创

python3 metaclass--创

作者头像
py3study
发布2020-01-13 11:06:14
3960
发布2020-01-13 11:06:14
举报
文章被收录于专栏:python3python3

之前学python的时候就看见过metaclass的文章,没看懂,那篇博客后面说到,metaclass是python的黑魔法99% 不会用到。于是果断放弃。

不过最近看flask-WTForm组建的源码,一开始就是metaclass。没办法,硬着头皮重新看metaclass。基本了解,现在总结如下:

一、metaclass干嘛的?

metaclass是指定类由谁创建。能够定制类的创建过程

指定类由谁创建的???开什么玩笑,类不是由'我'创建的吗????

python中一切皆对象,类也是对象,类是由type类创建。

我们写下如下代码时:

class Foo(object):
    pass

实际上,解释器将其解释为:

Foo = type('Foo', (object,), {})

type()的三个参数:'Foo':类名; (object, ): 类的继承关系,用元组表示; {}: 类的字段,方法。

以上是类的默认创建方法。由type创建。python也给我们提供了自定义类的创建的方法,即metaclass。type也是类,它可以创建类,因此我们叫它元类,不要过分纠结这是什么鬼,知道type类可以创建类就行。

自定义类的创建过程,那就得写一个像type一样可以创建类的类,那简单,继承就可以办到。

方式一:

class MyType(type):
    
    def __new__(cls, *args, **kwargs):
        print('MyType __new__')
        return super().__new__(cls, *args, **kwargs)
    
    def __init__(cls, *args, **kwargs):
        print('MyTpye __init__')
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('MyTpye __call__')
        super().__call__(cls, *args, **kwargs)


class Foo(metaclass=MyType):
    pass

这样,解释器解释到class Foo(...)的时候,就会转换为:

Foo = MyType('Foo', (object,), {})

方式二:

class MyType(type):
    def __new__(cls, *args, **kwargs):
        print('MyType __new__')
        return super().__new__(cls, *args, **kwargs)
    
    def __init__(cls, *args, **kwargs):
        print('MyTpye __init__')
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        print('MyTpye __call__')
        super().__call__(cls, *args, **kwargs)


def with_meta(meta, Base):
    return meta('Foo', (Base, ), {})


class Foo(with_meta(MyType, object)):
    pass

这样解释的时候,与方式一的一样。

二、创建类与类实例化时执行过程是怎样的?

解释器解释到class的定义语句时,会先在class中寻找是否指定自定义的'MyType', 没有再往父类找是否指定,没有再在本模块中找,是否本模块指定了统一的'MyType', 若均没有,则用默认的type创建。

解释到class Foo(...)时,会调用'MyType'的__new__, __init__方法。生成类。

解释到f = Foo() ,类的实例化时,会调用'MyType'的__call__方法,而'type'的__call__方法又会去调用Foo的__new__, __init__实例化类对象。

下面用一个实际的例子来说明元类的使用方法

三、ORM的元类实例:

#ORM:object relational mapping 对象-关系映射
#把关系数据库的一行映射为一个对象,也就是一个类对应一个表
#ORM框架所有的类只能动态定义


# 定义Field(定义域:元类遇到Field的方法或属性时即进行修改)
class Field(object):

    def __init__(self, name, column_type):  # column==>列类型
        self.name = name
        self.column_type = column_type

    # 当用print打印输出的时候,python会调用他的str方法
    # 在这里是输出<类的名字,实例的name参数(定义实例时输入)>
    # 在ModelMetaclass中会用到
    def __str__(self):
        return "<%s:%s>" % (self.__class__.__name__, self. name)  # __class__获取对象的类,__name__取得类名



# 进一步定义各种类型的Field
class StringField(Field):

    def __init__(self, name):
        # super(type[, object-or-type])  返回type的父类对象
        # super().__init()的作用是调用父类的init函数
        # varchar(100)和bigint都是sql中的一些数据类型
        super(StringField, self).__init__(name, "varchar(100)")  

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, "bigint")
# 编写ModelMetaclass
class ModelMetaclass(type):

    # __new__方法接受的参数依次是:
    # 1.当前准备创建的类的对象(cls)
    # 2.类的名字(name)
    # 3.类继承的父类集合(bases)
    # 4.类的方法集合(attrs)
    def __new__(cls, name, bases, attrs):
        # 如果说新创建的类的名字是Model,那直接返回不做修改
        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 mappings:%s ==> %s" % (k, v))  # 找到映射, 这里用到上面的__str__
                mappings[k] = v
            # 结合之前,即把之前在方法集合中的零散的映射删除,
            # 把它们从方法集合中挑出,组成一个大方法__mappings__
            # 把__mappings__添加到方法集合attrs中
        for k in mappings.keys():
                attrs.pop(k)
        attrs["__mappings__"] = mappings
        attrs["__table__"] = name # 添加表名,假设表名与类名一致
        return type.__new__(cls, name, bases, attrs)


# 编写Model基类继承自dict中,这样可以使用一些的方法
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self,  **kw):
        # 调用父类,即dict的初始化方法
        super(Model, self).__init__(**kw)

    # 让获取key的值不仅仅可以d[k],也可以d.k
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    # 允许动态设置key的值,不仅仅可以d[k],也可以d.k
    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))

# 这样一个简单的ORM就写完了# 下面实际操作一下,先定义个User类来对应数据库的表Userclass User(Model): # 定义类的属性到列的映射 id = IntegerField("id") name = StringField("username") email = StringField("email") password = StringField("password")# 创建一个实例u = User(id=12345, name="ReedSun", email="sunhongzhao@foxmail.com", password="nicaicai")u.save()

上面的代码按功能可以分为三部分:

1. 定义属性

class Field(object):
    pass

# 进一步定义各种类型的Field
class StringField(Field):
    pass
    
class IntegerField(Field):
    pass

2.  操作属性:

# 编写ModelMetaclass
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
      pass

# 编写Model基类继承自dict中,这样可以使用一些的方法
class Model(dict, metaclass=ModelMetaclass):

    def __init__(self,  **kw):
        # 调用父类,即dict的初始化方法
        super(Model, self).__init__(**kw)

    # 让获取key的值不仅仅可以d[k],也可以d.k
    def __getattr__(self, key):
       pass
    # 允许动态设置key的值,不仅仅可以d[k],也可以d.k
    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
      pass

# 下面实际操作一下,先定义个User类来对应数据库的表User
class User(Model):
    # 定义类的属性到列的映射
    id = IntegerField("id")
    name = StringField("username")
    email = StringField("email")
    password = StringField("password")

3. 统管属性:

u = User(id=12345, name="ReedSun", email="sunhongzhao@foxmail.com", password="nicaicai")
u.save()

代码执行流程:

解释器执行到 class Model 时,知道其指定了由 ModelMetaclass创建。因此,解释(不是执行)完类内定义的方法后,跳进 ModelMetaclass 的 __new__(cls, name, base, attrs), ModelMetaclass 没有__init__,执行type的__init__。(由于是生成Model, __new__(cls, name, base, attrs)中的clc为Model)  至此Model类正式创建完毕。

解释器执行到class User 时,与上面一样, 解释完类内定义的 id, name, email , password 字段后(这些字段均为...Field对象),跳进ModelMetaclass 的 __new__(cls, name, base, attrs),此时 cls 为 User, name 为 'User',  base 为 Model, attrs 为类似{'id': IntegerField("id"), 'name':StringField("username"),.........}的字典。

建议最好自己设置断点,调式执行看看,就会明白执行流程是怎么样的。

四、定制类生成的好处:

当然是非常多的,最容易理解的,单例模式便可以用 metaclass来实现。其他的好处,如ORM,我还说不上来,等弄清楚再补充。

参考与推荐博文:

https://blog.csdn.net/weixin_35955795/article/details/52985170

http://blog.jobbole.com/21351/

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000

https://www.cnblogs.com/xybaby/p/7927407.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档