菜鸟实现蹩脚ORM的过程与思考

本文将简要记录一下本人作为一个菜鸟是如何构建出一个蹩脚ORM系统的过程。首先说一下背景,大概去年七月当时刚开始学习编程,学习了廖雪峰老师的Python教程。当时做了最后的实战但是很多东西并没有特别清楚,尤其是ORM(Object Relational Mapping),当时写的云里雾里,完全不能理解为什么要这样写。现在时间过了很久,当时是如何实现的也基本已经忘光了,于是借着自己做博客玩的机会尝试按照自己的思路来造了一次ORM,非常的蹩脚,但是做出来后很多当时不能理解的地方也能理解了,故记录一下这个过程。

使用的第三方库有MySQLdb

Database

先简要说一下database的设计,非常简单,三个表,分别对应user, post, 和comment, 这里以post为例。包含的columns有post_id(PK), post_title, post_context以及其他一些并不那么重要的东西,这里就不一一列举了。

Class

然后开始撸代码,首先肯定是要有一个class来和post表进行对应,于是一开始的代码就是这样的(省略了若干columns)

class Posts:
    __table__ = "posts"
    def __init__(self, title, context):
        self.title = title
        self.context = context

这里可以发现在init中并没有传入post_id,因为当时的想法是Primary Key是自增的,不应该由我来设置,就没有进行传入。嗯,自我感觉良好,继续开始撸。

Insert

有了class就要开始写方法了,首先想到的自然是如何插入一个项目到表中(我已然忘了database的术语应该如何描述这个东西。。。。)。最开始的代码就不放了,因为当时写好了之后很快就被我抹掉然后进行抽象了(当时想着这个insert应该其他的所有类都会有,应该抽象一下出来)。于是有了如下代码

def insert(self, db):
    c = db.cursor()
    sql = insert_sql(self)
    try:
        rows = c.execute(*sql)
    except:
        print(c._last_executed)
        return False
    if rows != 0:
        db.commit()
        c.close()
        return True
    return False

其中db是全局变量,代表着连接的databasec._last_executed是最后执行的sql代码(因为一直是自己弄所以肯定会经常错,就用这个来看到底是哪里错了)。insert_sql就是抽象出来的部分,用来生成sql代码。这个逻辑很简单就不详细说了,主要说一下insert_sql的部分。

该如何实现insert_sql呢,不管三七二十一先写一点是一点

def insert_sql(obj):
    table = obj.__table__

然后就开始想了,我要怎么把各个columns的名字一一对应上呢?是不是应该把classattributecolumns一一对应起来,然后获取每一个attributes的名字,再进行插入?于是面向Google搜索了一下,得出结论,可以使用__dict__来获得各个attributes的名字。但是回头一看,发现其实自己class各个attribute的名字和数据表并不一一对应,于是做出了相应的改动。

    def __init__(self, title, context):
        self.post_title = title
        self.post_context = context
    
    def insert_sql(obj):
        attrs = obj.__dict__
        table = obj.__table__
        attrs_name = ", ".join(list(attrs.keys()))
        attrs_value = list(attrs.values())
        h = "%s," * len(attrs_value)
        sql = ("INSERT INTO {} ({}) values ({})".format(table, attrs_name, h[:-1]), (*attrs_value,))
        return sql

从这里开始就感觉不是很好了,每一个属性都要一一对应,感觉是不是耦合度有点高?但是回头想一想感觉似乎也没有别的办法了?然后回头看了一下廖雪峰课程中的代码,发现似乎也是要一一对应的,就先放下了。

(这里有一个小插曲,在我写完第一个版本的insert_sql之后(和这个不一样),我尝试了插入markdown文档,然后发现完全乱七八糟的,各种转义都没有,然后发现我的sql语句生成使用的是format来生成一个string,而MySQLdbexecute其实是支持更加安全的转义的,是通过在string中加入%号和传入另外一个参数,这个参数为一个tuple,和每一个%一一对应,于是做出了更改。所以在这里返回的sql其实是一个tuple而返回之后的执行也是通过*来进行结构之后传入到execute

Select

insert告一段落开始写select,刚开始没多久我就发现似乎有点怪。。我怎么做到在已经初始一个实例之后再来进行选择呢。。回想了一下想起来class都有一个装饰器@classmethod来将一个方法转变为这个类的方法。Google了一下确认(真是开局一个def,剩下全靠Google),然后开始继续开心地撸码。

    @classmethod
    def select(cls, db, limit=None, where=None, order=None, desc=True):
        c = db.cursor(MySQLdb.cursors.DictCursor)
        sql = select_sql(cls, limit, where, order, desc)
        rows = c.execute(sql)
        if rows == 0:
            return False
        res = c.fetchall()
        ret = []
        for p in res:
            post = cls(p["post_title"], p["post_context"], p["post_date"], p["post_abstract"], p["post_comments"], p["post_viewed"], p["post_id"])
            ret.append(post)
        return ret

这里就很明显能看到这个方法的问题了,最明显的就是我得一个个attribute输进去,怎么能忍!!!不过我还是忍了(懒。。)。如果就这样写的话甚至都无法抽象出来放到Post的父类中去作为方法继承下来。要抽象出来的话其实很麻烦,能想到的办法是多加一层for循环把每一个p中的属性取出来存到一个list中再传到实例构建中,但是p作为一个字典他是无序的,所以没有办法确保生成的顺序,想来想去还是改metaclass,也就是廖雪峰老师的办法,因为之前已经写过就并没有再改了(懒。。。)。(临时写着又想了一个办法,就是在实例生成的时候直接传入一个dict,然后生成的时候根据dict的属性来一一生成,但是这样似乎并不是很安全)。

当时想着也不是不能用就继续了,select_sql的实现也和insert_sql差不多

def select_sql(obj, limit=None, where=None, order=None, desc=True, columns = "*"):
    table = obj.__table__
    sql = "SELECT {} FROM {}".format(columns, table)
    if limit:
        sql += " limit {}".format(limit)
    if where:
        sql += " where {}".format(where)
    if order:
        sql += " order by {}".format(order)
        if desc:
            sql += " desc"
    sql += ";"
    return sql

(可以发现我虽然设置了columns但是并没有使用他。。真是懒癌晚期,这样就导致系统在抓取数据的时候速度会降低。不过其实当时没有使用columns来进行选择的另外一个原因是如果我不全部抓取我就无法生成实例,不过后来想想,我并没有必要传回一个实例,现在再想想,如果我是通过一个dict作为__init__的参数进行传入,也同样不用担心这个问题)

总结一下吧

简单而言就是这样了,剩下的update,delete也都是大同小异,就不详细说了(我才不会说其实我delete并没有做)。

一路写下来的感受就是不适用metaclass确实是非常的不灵活,诚如董明伟所言 ,这样的操作非常不优雅(都要写吐了)。

转载于:https://www.cnblogs.com/cuichenli/p/9580677.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值