SQLAlchemy更新操作的优雅方式

在介绍SQLAlchemy更新操作之前,先来思考一下下面的问题:

class Test(object):
    def __init__(self):
        self.a = ""
        self.b = ""


if __name__ == "__main__":
    obj1 = Test()
    obj2 = Test()
    obj2.a = "xixi"
    obj2.b = "haha"

如何在不改变obj1对象的内存地址的情况下将obj2对象的属性值全部赋给obj1对象?

你可能想到用__dict__这个魔术方法来实现上面的需求:

if __name__ == "__main__":
    obj1 = Test()
    obj2 = Test()
    obj2.a = "xixi"
    obj2.b = "haha"

    print(obj1.__dict__)
    for key in obj2.__dict__:
        setattr(obj1, key, getattr(obj2, key))

完美! 了解了上述方式之后我们进入文章的主题。


class HttpProxyDao(object):
	...省略非重点
    def update(self, update_obj):
        try:
            object_host = update_obj.ip
            object_port = update_obj.port
            proxyobj = self.session.query(HttpProxy).filter(HttpProxy.ip.like(object_host),
                                                            HttpProxy.port.like(object_port)).one_or_none()

            if proxyobj:
                for key in update_obj.__dict__:
                    # setattr(proxyobj, key, update_obj.__dict__[key])
                    # proxyobj.__setattr__(key, update_obj.__dict__[key])  # 都行
                    setattr(proxyobj, key, getattr(update_obj, key))

                self.session.commit()

            return proxyobj
        except Exception as e_update:
            print("e_update:", e_update)
            return None

我们利用下面的语句测试一下:

if __name__ == "__main__":
    obj = HttpProxyDao()
    httpobj = HttpProxy()
    httpobj.ip = "963"
    httpobj.port = "123"
    httpobj.type = "test"
    httpobj.speed = "966"
    rest = obj.update(httpobj)

    print(rest)

运行之后,惊讶的发现数据库里面并没有任何的变化。 也是是说,刚才写的更新方法压根没起到更新的作用。

怎么回事呢? 写一些语句辅助调试一下:

    def update(self, update_obj):
        try:
            object_host = update_obj.ip
            object_port = update_obj.port
            proxyobj = self.session.query(HttpProxy).filter(HttpProxy.ip.like(object_host),
                                                            HttpProxy.port.like(object_port)).one_or_none()

            if proxyobj:
                print(proxyobj.__dict__)  # 调试
                for key in update_obj.__dict__:
                    setattr(proxyobj, key, getattr(update_obj, key))

                print(proxyobj.__dict__)  # 调试
                self.session.commit()

            return proxyobj
        except Exception as e_update:
            print("e_update:", e_update)
            return None

运行结果:

{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000014993B3D208>, 'survival_time': None, 'speed': None, 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': '', 'server_address': None, 'country': None}
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x0000014993AD7048>, 'survival_time': None, 'speed': '966', 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': 'test', 'server_address': None, 'country': None}
<data_acquisition.data_acquisition.domain.HttpProxy.HttpProxy object at 0x0000014993B3D1D0>

我们发现,明明type,speed这两个属性的值都已经改变,但为啥还是没能修改呢?

仔细看一下会发现,虽然对象的名称都一样,但是内存地址却是不一样的。所以我们判定,proxyobj对象中一定有个属性,它将对象的内存地址给修改了。我们接着将属性的名称打印出来看一下:

    def update(self, update_obj):
        try:
            object_host = update_obj.ip
            object_port = update_obj.port
            proxyobj = self.session.query(HttpProxy).filter(HttpProxy.ip.like(object_host),
                                                            HttpProxy.port.like(object_port)).one_or_none()

            if proxyobj:
                for key in update_obj.__dict__:
                    print(key)
                    setattr(proxyobj, key, getattr(update_obj, key))

                print(proxyobj.ip, "更新成功")
                self.session.commit()
            return proxyobj
        except Exception as e_update:
            print("e_update:", e_update)
            return None

执行结果:

_sa_instance_state
ip
port
type
speed

果然,_sa_instance_state是个什么玩意?? 进一步,带着好奇心我们查看一下:


if proxyobj:
    print(proxyobj.__dict__)
    for key in update_obj.__dict__:
        print(getattr(proxyobj, '_sa_instance_state'))
        setattr(proxyobj, key, getattr(update_obj, key))
    print(proxyobj.__dict__)
    self.session.commit()
return proxyobj

{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x000002437233D278>, 'survival_time': None, 'speed': None, 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': '', 'server_address': None, 'country': None}
<sqlalchemy.orm.state.InstanceState object at 0x000002437233D278>
<sqlalchemy.orm.state.InstanceState object at 0x00000243722D60F0>
<sqlalchemy.orm.state.InstanceState object at 0x00000243722D60F0>
<sqlalchemy.orm.state.InstanceState object at 0x00000243722D60F0>
<sqlalchemy.orm.state.InstanceState object at 0x00000243722D60F0>
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x00000243722D60F0>, 'survival_time': None, 'speed': '966', 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': 'test', 'server_address': None, 'country': None}
<data_acquisition.data_acquisition.domain.HttpProxy.HttpProxy object at 0x000002437233D240>

第一次迭代,_sa_instance_state的值是proxyobj对象的内存地址,后面几次就被修改了。
使用猜想_sa_instance_state属性应该是proxyobj对象的内存地址。

使用,现在我们只要过滤掉这个属性,就可以完成操作。我们接着修改代码:

def update(self, update_obj):
    try:
        object_host = update_obj.ip
        object_port = update_obj.port
        proxyobj = self.session.query(HttpProxy).filter(HttpProxy.ip.like(object_host),
                                                        HttpProxy.port.like(object_port)).one_or_none()

        if proxyobj:
            print(proxyobj.__dict__)
            for key in update_obj.__dict__:
                if key == '_sa_instance_state':
                    continue
                setattr(proxyobj, key, getattr(update_obj, key))

            print(proxyobj.__dict__)
            self.session.commit()
        return proxyobj
    except Exception as e_update:
        print("e_update:", e_update)
        return None

运行:

{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x000001DEC414D2B0>, 'survival_time': None, 'speed': None, 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': '', 'server_address': None, 'country': None}
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x000001DEC414D2B0>, 'survival_time': None, 'speed': '966', 'anonymity': None, 'port': '123', 'ip': '963', 'verify_time': None, 'connection_time': None, 'type': 'test', 'server_address': None, 'country': None}
<data_acquisition.data_acquisition.domain.HttpProxy.HttpProxy object at 0x000001DEC414D278>

好了,内存地址没变。查看数据库中的值也修改成功了。

结束语

这里的坑是_sa_instance_state这个属性,其实这个属性在一开始的那个例子中是不存在的,所以在遇到这种问题的时候,如果发现结果不对劲,还是将key(属性值)打印出来看一下。

你可能感兴趣的:(SQLAlchemy,更新,ORM,日常BUG,SQLAlchemy)