Flask-SQLAlchemy通过Class获取定义的表结构原理浅析,什么是元类?
  • 分类:Python
  • 发表:2019-08-29
  • 围观(4,160)
  • 评论(0)

问题来源

在Flask项目中,通过SQLAlchemy定义好了模型类,但是执行SQLAlchemy的create_all方法后数据表并没有在数据库中创建。这个问题对于新手来说肯定遇见过,通过网上查询可以得知我们定义的模型类必须通过import引入到项目当中去,只用这样SQLAlchemy才能够获取到我们定义的model class的信息。这种Bug很容易避免,因为一个项目中定义的模型类肯定会被调用,如果该类没有被调用过那么这个类存在也是没有意义的。即使是我们刚刚完成模型类的定义,我们只需要保证模型类被import过一次就可以了,问题就解决了。

显然为了这么一个小问题专门写篇文章有点兴师动众的感觉,本文章的目的也不在于此。真正使我好奇的是为什么一个类在只调用import不调用其他方法的前提下,SQLAlchemy可以获取到它的类信息,然后通过这个信息创建相应的数据表呢?

问题解决

模型表数据

通过单步调试,我发现在import模型类之前和import模型类之后,实例化的SQLAlchemy对象下面的db.Model.metadata.tables属性存储着数据表的信息,下面的db.create_all()也是获取该属性下的数据创建相应的数据表的。虽然知道信息是存储在这个属性下但是并没有解决我最初的疑问,信息是在哪里赋值给该属性的?

 

因为我是个菜鸡我尝试单步调试,我想通过Pycharm的F7把它单步跟出来。但是任凭我按了十分钟的F7,内部代码还是没有跑完。内部的循环和底层代码太多了,或许是我没有掌握调试的真正奥义,反正这条路锁死。

此路不同另辟蹊径咯。因为我菜,我想从源码入手,看懂源码可不就把我的疑问解决了。但是方向又错了,我天真的认为Flask有什么底层的方法,会在执行import的时候会调用该方法将表结构导入到SQLAlchemy对象当中。于是我就去查相应的资料,Python的import机制。结果并不乐观,貌似根本没有这个神秘方法,还是要认真看Flask-SQLAlchemy的代码。


跟群里探讨大神探讨了一下,他告诉我ORM框架的实现用到了元类。继承自元类的类会在代码中动态创建,创建的过程中就会执行定义对应的方法。而就是在这个方法中会讲orm的模型表信息传给实例化的SQLAlchemy对象供后面create_all使用。

SQLAlchemy类的Model属性

基于这个理解,我完全仿照Flask-SQLAlchemy的代码逻辑写了一小段测试代码。执行test.py文件通过输出我们能够观察到一些有意思的现象

from myfile import SQLAlchemy_test

db = SQLAlchemy_test()

if __name__ == '__main__':
    from device import Device, Android
class SQLAlchemy_test:
    def __init__(self):
        self.Model = self.make_declarative_base()

    def make_declarative_base(self):
        return Weiney()


class Weiney():
    def __init__(self, *args):
        print(args)
from file1 import db


class Device(db.Model):
    print("Device类被导入")

    def get_value(self):
        print("Device value is xxx")


class Android(db.Model):
    print("Android类被导入")

    def get_value(self):
        print("Android value is xxx")
测试代码输出结果

从输出信息可以看出,继承自db.Model类的model类在import的时候会通过方法实例化一个定制类,而且我们定制的model类中所有的属性都作为参数隐式传递到这个类中。这个就是元类中的metadata,通过metadata ORM模型可以获取用户自定义的类中的所有属性和方法定制出符合ORM规范的类。这也是动态语言强大之处所在。

什么是元类

基于网上现有的教程我对元类有了大致的一个理解,引自廖雪峰博客的解释:“当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。连接起来就是:先定义metaclass,就可以创建类,最后创建实例。所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。”

说起来有点绕,目的就是可以动态创建相应的对象?大概是这么个理解,我其实也是一知半解。廖雪峰大大也说这是Python语法中最难的魔术代码,基本上用不到但是这里还是被我给碰见了不是?也不得不说Flask的代码是十分Pythonic。七月老师也说过把Flask为数不多的源码看透彻之后,Python的功力会大大提升,诚不欺我。

如果对这个特性感兴趣的话,可以研究一下廖雪峰大大的博客使用元类-廖雪峰的官方网站。廖大大基于metaclass实现了一个简单的ORM模型,可以说和我的问题很应景了。想更深入的了解的话可以看看StackOverflow上一篇很火的文章:What are metaclasses in Python?这篇文章据说写的很全面,但是我还没有具体研究呢,嘿嘿嘿嘿😀。伯乐在线有转载的中文翻译。

总结

Python学习中真的有太多的坑需要填了,群里咨询大神的时候就有人说你纠结这些干嘛,按照文档就那样用不就行了么?但是我这个有点精神洁癖,这个问题真的是纠结了我一个月这么久了。还是感谢群内得大神仗义相助,给了我新的看待问题的思路,同时他还赞赏了我的学习方法,真是见笑了。

顺便吐槽一下现在的群的风气,问个问题上来就是问百度的表情包,问题没回答就算了上来就嘲讽就很让人生气不是?也不知道你有没有嘲讽我的资格。现在的社会风气真是在不断积压我们菜鸡的学习空间呢。问个问题都畏手畏脚生怕大佬觉得我们态度不行,真是卑微,比华晨宇脚下踩的音响还卑微。

Python总是有许多魔术代码,有一些解释器机制在基础教程里是接触不到的。我最近在Github看到一个项目叫:What the f*ck Python!讲的就是Python中的隐藏的一些小特性,虽然有的用处不是很大,而且不常接触,但是了解一下还是觉得十分有趣,建议有兴趣的可以看一看呢。


共有 0 条评论

Top