SQL-Alchemy Column的default参数问题,如何实现Column初始化赋值
  • 分类:Python
  • 发表:2019-09-07
  • 围观(6,815)
  • 评论(0)

SQL-Alchemy是基于Python的一个对象关系映射框架。基于SQL-Alchemy我们可以在不编写原生SQL的前提下通过框架生成我们想要的数据表。基于数据库编写的业务逻辑也同样不需要编写原生SQL,ORM框架会根据根据编写的Python代码对SQL进行自动生成,这极大的简化了开发者的工作流程。Flask的数据库操作也是围绕着SQL-Alchemy展开的。

在Flask-SQLAlchemy中,一个Model类就对应数据库中的一张表。一个Model下面包含若干Column,而一个Column就代表数据表中的一个字段。在定义Column时,Column有许多参数给我们定制。当然在大多数情况下我们只需要对其中的一两个参数进行赋值,大多数的参数我们只需要了解即可,这些只在有需要的情况下进行配置。这边以default参数为问题入口,对default参数的功能进行分析,同时顺便对常用的参数进行介绍

问题来源

from sqlalchemy import Column, String, Integer
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class Test(db.Model):
    __tablename__ = "weiney"
    id = Column(Integer, primary_key=True)
    name = Column(String(11), default="佚名")
    query_time = Column(Integer(11), default=0)


test = Test()
test.query_time += 1

我定义了一个Model类,我贴心的给相应的Column设置了default属性。但是在我真正使用的时候,在调用query_time属性自加的时候还是报了一个unsupported operand type(s) for +: 'NoneType' and 'int'错误,这个错误的意思就是None类型和int类型不能直接做加减。按照剧本的发展😂,设置了default属性的话实例初始值也应该是设置的值啊,结果不尽然。根据我走单步代码的结果显示,这个值并没有被赋予初始值,值竟然是None不是我们定义的default值,这是我发现的第一个问题。

第二个问题我发现通过即使定义了default属性,但是数据库内的字段default属性仍然不是我们在代码中定义的值,数据库中的default值还是Null。这就与我的认知出现了偏颇了呀(;′⌒`)。赶快去网上查查资料看看是什么问题。

问题解决

通过网上查询资料啊大概得出了问题的答案。为什么default值没有被赋值?而且数据库的default值与定义的default不一致?首先是第一个问题,在SQL-Alchemy中定义的表结构class仅仅是用作声明数据表。定义好的数据表会存在实例化的SQL-Alchemy对象中。所以我们实例化的Model对象只是做了简单的映射,所有的default值都是在执行db.session.commit()方法之后添加的。

这同样解释了,为什么我给default设置的是一个函数调用,我想让每条数据的字段都可以根据方法动态的生成,但是真实情况却是插入的所有数据的default值都是相同的。因为这个方法在Model类被引入到项目的时候就被执行并且被记录到实例化的SQL-Alchemy对象当中,这个方法只会被调用一次,所以存入的信息是一个固定的值。如果想达到我们想要的目的我们不能传入这个函数的调用,而是应该把函数本身作为参数传递进去,这样在SQL-Alchemy执行插入的时候就会调用这个方法从而实现我们的目的。这个小技巧的主要应用场景是需要给每条数据插入create_time时。

第二个问题就是为什么数据库中的default与我们定义的default值不一致?Column定义了两个default参数,分别是default和server_default。常用的default参数只是定义在SQL-Alchemy层的默认值,如果想定义数据库层面的default的属性应该用server_default,这并没有什么难度。至于到底是选用default还是server_default:why-isnt-sqlalchemy-default-column-value-available-before-object-is-committed这篇文章给出了相应的解释。


如果我想实现在实例化Model对象的时候将设置的default值添加到对应的属性下面我应该怎么做呢?简单的方法就是给每一个model类实现一个构造函数,在构造函数里面对相应的属性进行赋值。但是这样写太复杂了,每一个类都写构造函数确实很麻烦。我想到的是写一个基类,基类实现构造方法之后子类只需要继承就行了。问题是我们如何在构造函数中获取我们定义的表信息呢。我下面提出我的不成熟的解决方法。

因为在上篇文章Flask-SQLAlchemy通过Class获取定义的表结构原理浅析,什么是元类?我知道了,定义的表结构在导入到项目之后都会保存在SQL-Alchemy对象下的Model.metadata.tables属性下面。我们可以根据这个字典获取定义的属性名和default值,然后在构造函数中动态的给属性赋值。

from sqlalchemy import Column, String, Integer
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class Base(db.Model):
    __abstract__ = True  # 定义为基类

    def __init__(self):
        # 获取当前数据表的表名,对于有__tablename__属性的数据表则选用该属性
        # 否则按照SQL-Alchemy对表的命名规则,取类的小写
        tablename = self.__tablename__ if self.__tablename__ else self.__class__.__name__.lower()
        table = db.Model.metadata.tables[tablename]
        for key, value in table.columns.items():
            if value.default:
                setattr(self, key, value.default.arg)


class Test(Base):
    # __tablename__ = "test"
    id = Column(Integer, primary_key=True)
    name = Column(String(11), default="佚名")
    age = Column(Integer, default=0)

    def __init__(self):
        super().__init__()


test = Test()
print(test.name, test.age)
test.age += 1
print(test.age)

通过这样就实现了我们想要的功能,即使我现在觉得这个功能好像是无关紧要的。但是这是驱动我仔细翻阅文档的原动力,在这个过程自己的能力肯定是得到了一定的提升,学习不就是这样么?即使我仍然感觉我的代码水平是十分拙劣的。但是慢慢来,我相信在不久的将来我也可以写出Pythonic的代码。也希望有看到这篇文章的大神可以指出我的不足,感激不尽🙇‍🙇‍🙇‍

既然都看到这里了,下篇文章基于SQL-Alchemy Column代码内部的doc,对Column所有的parm做个小的介绍,也作为自己学习Flask的记录。

平凡的坚持终成伟大,洗洗睡咯!

共有 0 条评论

Top