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

Резюме: Ако модифицирам атрибут директно, ангажиментът работи добре. Вместо това, ако модифицирам атрибут чрез речника на класа, тогава, без значение как го модифицирам след това, той не извършва промени в db.

Някакви идеи?

Благодаря предварително

АКТУАЛИЗАЦИЯ 1:

Освен това това актуализира Health в db, но не и Hunger:

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 екземпляр речник и зададете друга стойност за 'desc', заобикаляте протокола на дескриптора (вижте Извикване на дескриптори):

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

Тук току-що създадохме нов екземпляр атрибут, наречен '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