SQLAlchemy фиксирует изменения объекта, измененного с помощью __dict__

Я разрабатываю многопользовательскую игру. Когда я использую объект из инвентаря, он должен обновить статистику пользовательского существа значениями атрибутов объекта.

Это мой код:

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

Вывод: если я изменяю атрибут напрямую, фиксация работает нормально. Вместо этого, если я изменяю атрибут через словарь класса, то, независимо от того, как я изменяю его впоследствии, он не фиксирует изменения в базе данных.

Любые идеи?

заранее спасибо

ОБНОВЛЕНИЕ 1:

Кроме того, это обновляет здоровье в базе данных, но не голод:

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

Таким образом, просто доступ к словарю не является проблемой для атрибутов в целом, но изменение атрибута через словарь предотвращает фиксацию изменений в этих атрибутах.

Обновление 2:

В качестве временного исправления я переопределил функцию __set_item__(self) в классе Creatures:

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:

Просмотрев предложения в ответах, я остановился на этом решении:

In 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

person user1735527    schedule 09.06.2013    source источник


Ответы (2)


Проблема связана не с SQLAlchemy, а с механизмом дескрипторов Python. Каждый атрибут 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', вы игнорируете протокол дескриптора (см. Вызов дескрипторов):

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

Здесь мы только что создали новый атрибут instance с именем 'desc' со значением 0. Метод Desc.__set__ никогда не вызывался, и в вашем случае у SQLAlchemy не было возможности «перехватить» назначение.

Решение состоит в том, чтобы использовать setattr, что в точности эквивалентно написанию a.desc:

setattr(a, 'desc', 1)   # Prints '__set__'
person icecrime    schedule 09.06.2013
comment
Прежде чем отметить это как ответ, у меня есть еще 1 вопрос и 1 замечание: 1) Почему второй вызов cur_creature.Health = 100 не вызывает set после того, как я изменил атрибут через словарь 2) ' setattr()' не вызывает KeyError, если я указываю атрибут, которого еще нет в классе - person user1735527; 10.06.2013
comment
1) Как только вы определяете атрибут instance с помощью __dict__, он скрывает атрибуты class из-за правил поиска атрибутов Python. - person icecrime; 10.06.2013
comment
2) setattr(obj, 'a', 0) эквивалентно obj.a = 0, поэтому нет, он не вызовет KeyError. Однако вы можете использовать hasattr() для проверки существования атрибута. - person icecrime; 10.06.2013
comment
@@icecrime Готово. Спасибо, это помогло мне прийти к окончательному решению и многое прояснило. - person user1735527; 10.06.2013
comment
@icecrime, спасибо! Пять часов поиска решения этой проблемы, ваш был единственным, который обратился к моей актуальной проблеме и указал мне на решение. - person Anson VanDoren; 24.03.2019

Не используйте __dict__. Используйте getattr и setattr для изменения атрибутов по имени:

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

Больше информации:

setattr

getattr

person korylprince    schedule 09.06.2013