SQLAlchemy 提交对通过 __dict__ 修改的对象的更改 [英] SQLAlchemy commit changes to object modified through __dict__

查看:23
本文介绍了SQLAlchemy 提交对通过 __dict__ 修改的对象的更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am developing a multiplayer game. When I use an object from inventory, it should update the user creature's stats with the values of the attributes of an object.

这是我的代码:

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)

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

Now, this doesn't commit things properly for some reason, so I tried:

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

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

Which works, but is not very convenient (since I would need a big if statement to updated the different stats of the creature)

所以我尝试了:

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

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

I get 100 in logs, but no changes, so I tried:

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

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

Still '100' in logs, but no changes, so I tried:

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

仍会在日志中写入 100,但不会向数据库提交更改.现在,这很奇怪,因为它仅在工作版本上有所不同,因为它在顶部有这一行:

Which still writes 100 in the logs, but doesn't commit changes to the database. Now, this is weird, since it only differ by the working version for the fact that it has this line on top:

cur_creature.__dict__['Health'] = 100

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

Summary: If I modify an attribute directly, commit works fine. Instead, if I modify an attribute through the class' dictionary, then, no matter how I modify it afterwards, it doesn't commit changes to the db.

有什么想法吗?

提前致谢

更新 1:

此外,这会更新数据库中的 Health,但不会更新 Hunger:

Also, this updates Health in the db, but not Hunger:

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

因此,一般来说,访问字典对于属性来说不是问题,但是通过字典修改属性会阻止提交对该属性的更改.

So just accessing the dictionary is not a problem for attributes in general, but modifying an attribute through the dictionary, prevents the changes to that attributes from being committed.

更新 2:

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

As a temporary fix, I've overridden the function __set_item__(self) in the class Creatures:

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

所以'use object'的新代码是:

So that the new code for 'use object' is:

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:

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

By having a look at the suggestions in the answers, I settled down for this solution:

生物

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

推荐答案

问题不存在于 SQLAlchemy,而是由 Python 的 描述符机制.每个 Column 属性都是一个描述符:这就是 SQLAlchemy 如何挂钩"属性检索和修改以生成数据库请求.

The problem does not reside in SQLAlchemy but is due to Python's descriptors mechanism. Every Column attribute is a descriptor: this is how SQLAlchemy 'hooks' the attribute retrieval and modification to produce database requests.

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

Let's try with a simpler example:

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' 设置另一个值,你就绕过了描述符协议(见 调用描述符):

However, if you go through a instance dictionary and set another value for 'desc', you bypass the descriptor protocol (see Invoking Descriptors):

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

在这里,我们刚刚创建了一个名为 'desc' 的新实例属性,其值为 0.Desc.__set__ 方法从来没有调用,在您的情况下,SQLAlchemy 将没有机会捕获"分配.

Here, we just created a new instance attribute called 'desc' with a value of 0. The Desc.__set__ method was never called, and in your case SQLAlchemy wouldn't get a chance to 'catch' the assignment.

解决方案是使用 setattr,完全等同于写a.desc:

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

这篇关于SQLAlchemy 提交对通过 __dict__ 修改的对象的更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆