Подтвердить что ты не робот

Элегантный способ избежать .put() на неизмененных объектах

Повторяющийся шаблон в моем программировании на Python в GAE - это получение некоторой сущности из хранилища данных, а затем, возможно, изменение этого объекта на основе различных условий. В конце концов мне нужно .put() вернуть объект обратно в хранилище данных, чтобы гарантировать, что любые изменения, которые могли быть сделаны с ним, будут сохранены.

Однако часто не было внесенных изменений, и окончательный .put() - это просто пустая трата денег. Как легко убедиться, что я только поместил объект, если он действительно изменился?

Код может выглядеть примерно как

def handle_get_request():
    entity = Entity.get_by_key_name("foobar")

    if phase_of_moon() == "full":
       entity.werewolf = True
    if random.choice([True, False]):
       entity.lucky = True
    if some_complicated_condition:
       entity.answer = 42

    entity.put()

Я мог бы поддерживать "измененный" флаг, который я установил, если какое-либо условие изменило сущность, но это кажется очень хрупким. Если я забуду установить его где-нибудь, тогда изменения будут потеряны.

Что я использовал

def handle_get_request():
    entity = Entity.get_by_key_name("foobar")
    original_xml = entity.to_xml()

    if phase_of_moon() == "full":
       entity.werewolf = True
    if random.choice([True, False]):
       entity.lucky = True
    if some_complicated_condition:
       entity.answer = 42

    if entity.to_xml() != original_xml: entity.put()

Я бы не назвал это "элегантным". Элегантный был бы, если бы объект просто автоматически сохранялся автоматически в конце, но я чувствовал, что это было просто и доступно для чтения, чтобы сделать это сейчас.

4b9b3361

Ответ 1

Почему бы не проверить, равен ли результат (==) оригинал, и поэтому решить, сохранять ли его. Это зависит от правильно реализованного __eq__, но по умолчанию сравнение по каждому полю, основанное на __dict__, должно это сделать.

  def __eq__(self, other) : 
        return self.__dict__ == other.__dict__

(Убедитесь, что другие богатые команды сравнения и хэша работают правильно, если вы это сделаете. См. здесь.)

Ответ 2

Одним из возможных решений является использование обертки, которая отслеживает изменение любого атрибута:

class Wrapper(object):
    def __init__(self, x):
        self._x = x
        self._changed = False

    def __setattr__(self, name, value):
        if name[:1] == "_":
            object.__setattr__(self, name, value)
        else:
            if getattr(self._x, name) != value:
                setattr(self._x, name, value)
                self._changed = True

    def __getattribute__(self, name):
        if name[:1] == "_":
            return object.__getattribute__(self, name)
        return getattr(self._x, name)

class Contact:
    def __init__(self, name, address):
        self.name = name
        self.address = address


c = Contact("Me", "Here")
w = Wrapper(c)

print w.name               # --> Me
w.name = w.name
print w.name, w._changed   # --> Me False
w.name = "6502"
print w.name, w._changed   # --> 6502 True

Ответ 3

Этот ответ является частью вопроса, который я опубликовал о контрольной сумме Python для диктата С ответами на этот вопрос я разработал метод генерации контрольной суммы из a db.Model.

Это пример:

>>> class Actor(db.Model):
...   name = db.StringProperty()
...   age = db.IntegerProperty()
... 
>>> u = Actor(name="John Doe", age=26)
>>> util.checksum_from_model(u, Actor)
'-42156217'
>>> u.age = 47
>>> checksum_from_model(u, Actor)
'-63393076'

Я определил эти методы:

def checksum_from_model(ref, model, exclude_keys=[], exclude_properties=[]):
    """Returns the checksum of a db.Model.

    Attributes:
      ref: The reference og the db.Model
      model: The model type instance of db.Model.

      exclude_keys: To exclude a list of properties name like 'updated'
      exclude_properties: To exclude list of properties type like 'db.DateTimeProperty'

    Returns:
      A checksum in signed integer.
    """
    l = []
    for key, prop in model.properties().iteritems():
        if not (key in exclude_keys) and \
               not any([True for x in exclude_properties if isinstance(prop, x)]):
            l.append(getattr(ref, key))
    return checksum_from_list(l)

def checksum_from_list(l):
    """Returns a checksum from a list of data into an int."""
    return reduce(lambda x,y : x^y, [hash(repr(x)) for x in l])

Примечание: Для реализации base36: http://en.wikipedia.org/wiki/Base_36#Python_implementation

Edit: Я удалил return в base36, теперь эти функции выполняются без зависимостей. (Совет @Skirmantas)

Ответ 4

Не работает с GAE, но в той же ситуации я бы использовал что-то вроде:

entity = Entity.get_by_key_name("foobar")
prev_entity_state = deepcopy(entity.__dict__)

if phase_of_moon() == "full":
   entity.werewolf = True
if random.choice([True, False]):
   entity.lucky = True
if some_complicated_condition:
   entity.answer = 42

if entity.__dict__ == prev_entity_state:
    entity.put()