在未更改的实体上避免使用.put()的优雅方法

时间:2011-09-07 08:34:44

标签: python google-app-engine

我在GAE上的Python编程中重现的模式是从数据存储中获取一些实体,然后可能根据各种条件更改该实体。最后,我需要将.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 个答案:

答案 0 :(得分:4)

为什么不检查结果是否等于(==)原始结果,因此决定是否保存它。这取决于正确实施的__eq__,但默认情况下,基于__dict__的逐字段比较应该这样做。

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

(如果你这样做,请确保其他丰富的比较和哈希运算符正常工作。See here。)

答案 1 :(得分:4)

一种可能的解决方案是使用跟踪任何属性更改的包装器:

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

答案 2 :(得分:2)

此回答是我发布的关于Python checksum of a 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的建议)

答案 3 :(得分:1)

不适用于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()