`
Tyler_Long
  • 浏览: 6996 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
最近访客 更多访客>>
社区版块
存档分类
最新评论

SQLAlchemy 数据建模过程的改进

阅读更多

SQLAlchemy是python里面最好的orm框架(注意, 没有"之一"两个字), 不过它定义orm的过程比较繁琐, 要分别定义table和model, 然后在两者之间弄个mapper. 纯手工的过程就是这样的, 一步步来, 有点体力活的感觉. 其实我没有实际写过这种代码, 因为我不喜欢干体力活.

 

#纯手工建模的代码我也没写过, 这里空缺
 

也许正是因为这个问题,很多人更喜欢django的orm, 尽管后者远远不如SQLAlchemy强大. 题外话: django的orm跟django的问题是一样的: 做些常见的东西还行, 稍微有点复杂或者稍微有点特殊的情况, 会让你很头疼. 这东西是报社开发的, 比较适合报社使用, 其它情况慎用. 不过django admin是比较牛的, 快速开发一个数据录入系统非常合适. django admin是我离开的django后唯一怀念的东西.

 

后来SQLAlchemy为了让纯手工的过程简化, 推出了一个Declarative扩展. 之所以是个扩展, 是因为它并没有增加实质的功能, 提供的价值就是帮你把建模的过程简化,实际的东西还是通过那套model, table, mapper的机制来完成的. 它可以让你只定义model, 然后table和mapper自动帮你搞定. 听起来还不错, 工作量至少减少了一半以上. 一个例子:

 

class Manufacturer(Base):
    __tablename__ = 'manufacturer'
    id = Column(Integer, primary_key = True)
    name = Column(String(30))
 

 

按道理能搞到这个程度, 比django的orm也复杂不到哪里去了, 我还是觉得有个地方不是很爽, 就是定义一对多关系的时候, 需要先定义一个外键, 然后再定义一个relationship. 总感觉有点重复: 既然我都定义了外键了, 那么当然我期望有一个relationship啊. SQLAlchemy这么设计我能理解, 它是为了更大的灵活性, 毕竟很多东西不应该一下子写死.

 

class Car(models.Model):
    __tablename__ = 'car'
    id = Column(Integer, primary_key = True)
    name = Column(String(30))

    manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
    manufacturer = relationship('Manufacturer', backref = backref('cars', lazy = 'dynamic'))
 

 

其实这还不算太糟糕啦. 关键是, 如果Menufacturer生产了不只Car一种产品呢, 比如韩国三星, 好多产品都做, 什么电冰箱,洗衣机,显示器,内存条,光驱...etc. 在你为这些所有的东西建模的时候, 你都要为它们加上外键, 并且再加上一个relationship, 就跟上面的类似的两行代码. 这给人的感觉是明显违背了DRY(Don't Repat Yourself)原则. 像我这种软件设计的小鸟都看出问题来了, 不用说SQLAlchemy开发者也早就看出问题了, 于是他们推出了Mixin的方式来减少重复. 

 

再来段题外话. Mixin似乎是Ruby的专利(当然我没有去考究过是不是Ruby第一个有这概念的). Ruby没有多继承, 却有Mixin. Mixin能够实现多继承的大部分功能, 但是却更加简单. Ruby的作者Matz为此颇为得意. 而Python中是有多继承的, 但是却没有Ruby中Mixin的机制. 于是可以用多继承的方式来实现类似Mixin的效果, 来简化SQLAlchemy中创建一对多关系的过程.

 

class BaseMixin:
     @declared_attr
     def __tablename__(cls):
          return cls.__name__.lower()

     id =  Column(Integer, primary_key = True)

class ManufacturerMixin:
     @declared_attr
     def manufacturer_id(self):
          return Column(Integer, ForeignKey('manufacturer.id'))

     @declared_attr
     def manufacturer(cls):
          return relationship('Manufacturer', backref = backref(cls.__name__.lower() + 's', lazy = 'dynamic'))
 

 

可以看到, 只要继承了BaseMixin, 就自动会声明tablename为小写的类名, 并且自动添加了一个Integer类型的主键. 只要继承Manufacturer, 就会动拥有到menufacturer表的外键及相应的关系. 通过这种继承Mixin的方式, 极大提供了代码的可重用性, 减少了代码量:

 

class Car(Base, BaseMixin, ManufacturerMixin)
     name = Column(String(30))
 

 

非常棒! 现在Car这个类里面只剩下了一行代码了! 上述代码中一个地方不是很容易理解, 就是@declared_attr. 这是个function decorator, 看过它的源码, 实际上是个类(decorator本身可以是个类, 事实上只要是callable就可以了). 根据文档: Mark a class-level method as representing the definition of a mapped property or special declarative member name. @declared_attr is more often than not applicable to mixins, to define relationships that are to be applied to different implementors of the class. 我的理解大致就是说, 把相关属性附加到目标类上面. 毕竟Mixin是为了让别的类重用, 改变别的类才是它最终的目的. 这里就不去深究了, 再没有深入了解SQLAlchemy其它的部分的基础知识的前提下, 这个问题恐怕很难彻底搞清楚.

 

不过大家有没有一种感觉, 就是通过继承来实现Mixin的方式是可以进一步改善的? 比如上面的那个ManufactureerMixin类, 里面的代码虽然不多, 但写得很拖沓, 明明是两个字段而已, 却写成了两个方法, 并且这两个方法都加上了@declared_attr这个样的一个decorator.(并且这个decorator的真实含义不太容易弄明白, 就算你看了文档, 也只能说大致了解, 知其然不知其所以然). 于是我开始思考能不能在Python里面用另外一种方式来实现Mixin? 我想到了Python2.6中新出现的class decorator.(有人可能会问python2.5以及之前的版本怎么版呢? 我只能说凉拌, 你还用上面的通过继承来Mixin的方式就行了. Python2.6在今天已经是很古老的版本了, python2.7和python3.2稳定版都出来很久了, 谁还有心思去兼顾python2.5啊). 先上代码吧:

 

def manufacturer_mixin(cls):
          cls.manufacturer_id = Column(Integer, ForeignKey('manufacturer.id'))
          cls.manufacturer = relationship('Manufacturer', backref = backref(cls.__name__.lower() + 's', lazy = 'dynamic'))

@manufacturer_mixin
class Car(Base, BaseMixin)
          name = Column(String(30))
 

 

上面的代码是不是更加好了呢? 首先代码行短了很多, 其次添加decorator的方式似乎也比多继承要优雅些. 最让人高兴的是, 那个难以理解的"@declared_attr"不见了. 剩下的都是可以理解的代码.

 

不过现在还是不够, 因为每定义一个外键关系就得定一个相应的class decorator. 每个class decorator之中的代码都是类似的: 一个外键和一个relationship. 这显然还是不符合DRY原则的. 可不可以只定义一个class decorator就搞定所有的外键? 如果可以的话, 这个decorator可以放到通用库中, 供以后所有的需要定义数据库外键的项目中使用, 极大地提高代码的复用程度.

 

def foreign_key(table):
        """Class decorator, add a foreign key to a SQLAlchemy model.
        Parameter table is the destination table, in a one-to-many relationship, table is the "one" side.
        """
        def ref_table(cls):
            setattr(cls, '{0}_id'.format(table), Column(Integer, ForeignKey('{0}.id'.format(table))))
            setattr(cls, table, relationship(table.capitalize(), backref = backref(cls.__name__.lower() + 's', lazy = 'dynamic')))
            return cls
        return ref_table
 

 

上面的代码没有任何跟具体模型相关的代码, 所以它的通用性是非常强的. 理论上讲在所有的需要定义sqlalchemy模型的地方都可以使用, 不用一遍遍地重复代码了. 只要定义像上面的一个foreign_key class decorator, 就可以一行代码搞定一个外键关系, 比如:

 

@foreign_key('another_foreign_key')
@foreign_key('manufacturer')
class Car(Base, BaseMixin)
    name = Column(String(30))
 

 

到此为止吧. 编程有个原则, 叫做"不要过度优化"(Avoid Premature Optimization). 这个原则很多时候特指性能方面的优化. 在这里也勉强适用: 优化代码也得适可而止, 太过分的优化就是浪费时间.

 

 

 

1
2
分享到:
评论

相关推荐

    Python 使用Flask-SQLAlchemy实现数据的多对关系 Python源码

    Python 使用Flask-SQLAlchemy实现数据的多对关系 Python源码Python 使用Flask-SQLAlchemy实现数据的多对关系 Python源码Python 使用Flask-SQLAlchemy实现数据的多对关系 Python源码Python 使用Flask-SQLAlchemy实现...

    sqlalchemy documentation sqlalchemy 文档

    根据提供的文档信息,我们可以归纳出一系列关于SQLAlchemy的知识点,主要涵盖了概述与安装、对象关系映射教程、SQL表达式语言教程以及映射器配置等内容。以下是对这些知识点的详细阐述: ### 概述与安装 #### 1.1 ...

    sqlalchemy-utils, Sqlalchemy的各种实用程序函数和数据类型.zip

    这些数据类型扩展了SQLAlchemy的基础类型,使得我们能够更灵活地处理特定的数据格式。例如,`Money`类型用于存储货币值,它支持货币符号和小数位数的设置。`JSON`类型允许我们将JSON对象存储在数据库中,并提供了...

    SQLAlchemy

    使用SQLAlchemy,开发者可以通过Python代码定义数据表结构,并创建与之对应的类。例如: ```python from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, Unicode # 创建一个连接到...

    Python 使用Flask-SQLAlchemy创建数据表 Python源码

    Python 使用Flask_SQLAlchemy创建数据表 Python源码Python 使用Flask_SQLAlchemy创建数据表 Python源码Python 使用Flask_SQLAlchemy创建数据表 Python源码Python 使用Flask_SQLAlchemy创建数据表 Python源码Python ...

    Python利用flask sqlalchemy实现分页效果

    ### 使用Flask-SQLAlchemy实现分页效果 #### 引言 在Web开发中,当数据量较大时,为了提高用户体验以及减轻服务器负担,通常会采用分页技术展示数据。Flask-SQLAlchemy是一个非常流行的扩展,它为Flask框架提供了...

    sqlalchemy

    10. 映射类的继承层次:开发者可以使用SQLAlchemy映射类的继承层次,从而使得数据模型能够更好地反映现实世界的类层次。 11. 关系映射:在关系型数据库中,映射一对多、多对多等关系是常见需求,SQLAlchemy提供了...

    FlaskSQLAlchemy下载及安装

    **Flask-SQLAlchemy**是Python微框架...通过上述步骤,你可以轻松地在Flask应用中安装、配置和使用Flask-SQLAlchemy,实现数据的持久化存储。同时,利用其提供的ORM特性,可以极大地提高开发效率,简化数据库操作流程。

    sqlalchemy文档资料翻译

    - **Column and Data Types**:涵盖SQLAlchemy中的各种数据类型及其使用方法,以及如何自定义数据类型。 - **扩展与插件**:`sqlalchemy.ext` 包含了一系列的扩展和插件,这些插件可以增强SQLAlchemy的功能。 ###...

    SQLAlchemy技术文档(中文版)

    ### SQLAlchemy技术文档(中文版)知识点总结 #### 1. 版本检查 - **功能说明**:在使用SQLAlchemy之前,确保安装的版本符合项目需求是非常重要的。可以通过导入SQLAlchemy包并调用`__version__`属性来检查当前安装的...

    Essential SQLAlchemy 2nd Edition

    4. **数据类型**: SQLAlchemy支持多种数据类型,如Integer、String、DateTime等,它们与SQL中的数据类型相对应。定义模型时,我们需要为每个属性指定相应的数据类型。 5. **关系映射**: 除了基本的列映射,...

    SQLAlchemy 1.2.8

    SQLAlchemy 1.2.8是该库的一个稳定版本,提供了多种增强和改进,使得数据库交互更加便捷和高效。 1. **SQLAlchemy ORM**:ORM是SQLAlchemy的核心部分,它允许开发人员使用面向对象的方式来处理数据库操作。通过ORM...

    Python库 | SQLAlchemy-1.4.0b1.tar.gz

    在 SQLAlchemy-1.4.0b1 这个版本中,我们可能会看到以下更新和改进: - 新特性和功能:新版本可能引入了一些新的 API 或功能,以提高性能或添加更高级别的抽象。 - 优化:开发团队可能对已有的模块进行了优化,提高...

    Python SQLAlchemy入门教程(基本用法)

    文档描述了SQLAlchemy中的常见数据类型映射到Python数据类型的对应关系。例如,数据库中的int类型映射为Python的整型,varchar和text类型映射为字符串类型,datetime类型映射为Python的datetime类型。 5. 创建...

    SQLAlchemy最新英文文档

    因此,SQLAlchemy采用了类似于Java里Hibernate的数据映射模型,而不是其他ORM框架采用的Active Record模型。不过,Elixir和declarative等可选插件可以让用户使用声明语法。 SQLAlchemy首次发行于2006年2月,并迅速地...

    使用Flask SQLAlchemy创建数据表.zip

    本教程将深入探讨如何使用Flask和SQLAlchemy结合,来创建数据表。 首先,安装所需的库。确保已经安装了Flask、SQLAlchemy以及Flask-SQLAlchemy扩展。在命令行中运行以下命令: ```bash pip install flask flask-...

    SQLAlchemy 1.1 Documentation

    SQLAlchemy 1.1 Documentation

    SQLAlchemy详细教程

    SQLAlchemy 详细教程 SQLAlchemy 是一个 Python 的关系型数据库工具包,提供了一个高级的对象关系映射(Object-Relational Mapping,ORM)系统,允许开发者使用 Python 对象来交互数据库,而不需要直接使用 SQL ...

Global site tag (gtag.js) - Google Analytics