Элегантный способ избежать .put () на неизмененных объектах - PullRequest
12 голосов
/ 07 сентября 2011

Повторяющийся шаблон в моем программировании на 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()

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

Ответы [ 4 ]

4 голосов
/ 07 сентября 2011

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

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
4 голосов
/ 07 сентября 2011

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

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

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

2 голосов
/ 07 сентября 2011

Этот ответ является частью вопроса, который я написал о Контрольной сумме Python для dict С ответами на этот вопрос я разработал метод генерации контрольной суммы из 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

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

1 голос
/ 07 сентября 2011

Не работал с 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...