SQLAlchemy 将更改提交到通过 __dict__ 修改的对象

Posted

技术标签:

【中文标题】SQLAlchemy 将更改提交到通过 __dict__ 修改的对象【英文标题】:SQLAlchemy commit changes to object modified through __dict__ 【发布时间】:2013-06-05 12:33:49 【问题描述】:

我正在开发一款多人游戏。当我使用库存中的对象时,它应该使用对象属性的值更新用户生物的统计信息。

这是我的代码:

try:
    obj = self._get_obj_by_id(self.query['ObjectID']).first()

    # Get user's current creature
    cur_creature = self.user.get_current_creature()

    # Applying object attributes to user attributes
    for attribute in obj.attributes:
        cur_creature.__dict__[str(attribute.Name)] += attribute.Value

    dbObjs.session.commit()
except (KeyError, AttributeError) as err:
    self.query_failed(err)

现在,由于某种原因,这并没有正确提交,所以我尝试了:

cur_creature.Health  = 100
logging.warning(cur_creature.Health)
dbObjs.session.commit()

这可行,但不是很方便(因为我需要一个大的 if 语句来更新生物的不同统计数据)

所以我尝试了:

cur_creature.__dict__['Health'] = 100
logging.warning(cur_creature.Health)
dbObjs.session.commit()

我在日志中得到100,但没有任何变化,所以我尝试了:

cur_creature.__dict__['Health'] = 100
cur_creature.Health  = cur_creature.__dict__['Health']
logging.warning(cur_creature.Health)
dbObjs.session.commit()

日志中仍然是“100”,但没有变化,所以我尝试了:

cur_creature.__dict__['Health'] = 100
cur_creature.Health  = 100
logging.warning(cur_creature.Health)
dbObjs.session.commit()

仍然会在日志中写入 100,但不会将更改提交到数据库。 现在,这很奇怪,因为它只因工作版本不同而不同,因为它在顶部有这一行:

cur_creature.__dict__['Health'] = 100

总结:如果我直接修改一个属性,提交工作正常。相反,如果我通过类的字典修改一个属性,那么,无论我之后如何修改它,它都不会将更改提交到 db。

有什么想法吗?

提前致谢

更新 1:

此外,这会更新 db 中的 Health,但不会更新 Hunger:

cur_creature.__dict__['Hunger'] = 0
cur_creature.Health  = 100
cur_creature.Hunger = 0
logging.warning(cur_creature.Health)
dbObjs.session.commit()

所以一般来说,仅仅访问字典对属性来说不是问题,但是通过字典修改属性,会阻止对该属性的更改被提交。

更新 2:

作为一个临时修复,我在类 Creatures 中覆盖了函数 __set_item__(self)

def __setitem__(self, key, value):
    if key == "Health":
       self.Health = value
    elif key == "Hunger":
       self.Hunger = value

所以“使用对象”的新代码是:

try:
    obj = self._get_obj_by_id(self.query['ObjectID']).first()

    # Get user's current creature
    cur_creature = self.user.get_current_creature()

    # Applying object attributes to user attributes
    for attribute in obj.attributes:
        cur_creature[str(attribute.Name)] += attribute.Value

    dbObjs.session.commit()
except (KeyError, AttributeError) as err:
    self.query_failed(err)

更新 3:

通过查看答案中的建议,我确定了这个解决方案:

Creatures

def __setitem__(self, key, value):
    if key in self.__dict__:
       setattr(self, key, value)
    else:
       raise KeyError(key)

其他方法

 # Applying object attributes to user attributes
 for attribute in obj.attributes:
     cur_creature[str(attribute.Name)] += attribute.Value

【问题讨论】:

【参考方案1】:

问题不在 SQLAlchemy 中,而是由 Python 的 descriptors mechanism 引起的。每个Column 属性都是一个描述符:这就是 SQLAlchemy 如何“挂钩”属性检索和修改以产生数据库请求。

让我们尝试一个更简单的例子:

class Desc(object):
    def __get__(self, obj, type=None):
        print '__get__'
    def __set__(self, obj, value):
        print '__set__'

class A(object):
    desc = Desc()

a = A()
a.desc                  # prints '__get__'
a.desc = 2              # prints '__set__'

但是,如果您通过a instance 字典并为'desc' 设置另一个值,您将绕过描述符协议(请参阅Invoking Descriptors):

a.__dict__['desc'] = 0  # Does not print anything !

在这里,我们刚刚创建了一个名为 'desc' 的新 instance 属性,其值为 0。从未调用过 Desc.__set__ 方法,在您的情况下,SQLAlchemy 没有机会调用'抓住'任务。

解决方法是使用setattr,完全等价于写a.desc

setattr(a, 'desc', 1)   # Prints '__set__'

【讨论】:

在将此标记为答案之前,我还有 1 个问题和 1 条评论:1)为什么第二个调用“cur_creature.Health = 100”之后不调用 set我通过字典修改了属性 2) 如果我指定了一个不在类中的属性,'setattr()' 不会触发 KeyError 1) 一旦你通过__dict__ 定义了一个 instance 属性,由于 Python 的属性查找规则,它会隐藏 class 属性 2) setattr(obj, 'a', 0) 等同于 obj.a = 0 所以不,它不会引发 KeyError。但是,您可以使用 hasattr() 检查属性是否存在。 @@icecrime 完成。谢谢,它帮助我找到了最终的解决方案并澄清了很多事情。 @icecrime,谢谢!五个小时寻找解决方案,你是唯一解决我的实际问题并为我指明解决方案的人。【参考方案2】:

不要使用__dict__。使用getattrsetattr按名称修改属性:

for attribute in obj.attributes:
    setattr(cur_creature,str(attribute.Name), getattr(cur_creature,str(attribute.Name)) + attribute.Value)

更多信息:

setattr

getattr

【讨论】:

以上是关于SQLAlchemy 将更改提交到通过 __dict__ 修改的对象的主要内容,如果未能解决你的问题,请参考以下文章

使用 sqlalchemy 在刷新/提交时自动散列主键并使其持久化

Flask(flask_sqlalchemy)使用原生sql,多个数据库用法进行封装

基础入门_Python-模块和包.深入SQLAlchemy之列级别约束与表级别约束?

sqlalchemy:如何通过一个查询连接多个表?

Git:将提交合并到不同的分支

进行联接时如何更改 SqlSoup / SqlAlchemy 中的 FROM 子句?