Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
172 views
in Technique[技术] by (71.8m points)

python - SQLAlchemy commit changes to object modified through __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.

This is my code:

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()

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

So I tried:

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

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()

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()

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.

Any ideas?

Thanks in advance

UPDATE 1:

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.

Update 2:

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

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)

Update 3:

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

In Creatures

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

In the other method

 # Applying object attributes to user attributes
 for attribute in obj.attributes:
     cur_creature[str(attribute.Name)] += attribute.Value
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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__'

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 !

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.

The solution is to use setattr, which is exactly equivalent to writing a.desc:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...