在Python中复制Google App Engine数据存储中的实体,无需在编译时知道属性名称
在我写的一个Python Google App Engine应用中,我有一个存储在数据存储中的实体,我需要把它取出来,做一个完全一样的副本(除了键以外),然后再把这个实体放回去。
我该怎么做呢?特别是,在这个过程中有没有什么需要注意的地方,确保我得到的副本是我想要的,而不是别的东西。
补充说明:好吧,我试了一下,确实遇到了一些问题。我希望能以一种方式来复制这个实体,这样我在写代码的时候就不需要知道属性的名字。我的想法是这样做:
#theThing = a particular entity we pull from the datastore with model Thing
copyThing = Thing(user = user)
for thingProperty in theThing.properties():
copyThing.__setattr__(thingProperty[0], thingProperty[1])
这段代码执行没有错误……直到我尝试从数据存储中取出copyThing,这时我发现所有的属性都被设置为None(显然,用户和键除外)。所以很明显,这段代码在做一些事情,因为它把默认值替换成了None(所有属性都有默认值),但这完全不是我想要的。有什么建议吗?
7 个回答
16
这段内容是对Nick Johnson的优秀代码的一个扩展,目的是解决Amir在评论中提到的问题:
- ReferenceProperty的db.Key值不再需要通过多余的请求去数据存储获取。
- 现在你可以选择是否跳过带有
auto_now
和/或auto_now_add
标志的DateTime属性。
以下是更新后的代码:
def clone_entity(e, skip_auto_now=False, skip_auto_now_add=False, **extra_args):
"""Clones an entity, adding or overriding constructor attributes.
The cloned entity will have exactly the same property values as the original
entity, except where overridden. By default it will have no parent entity or
key name, unless supplied.
Args:
e: The entity to clone
skip_auto_now: If True then all DateTimeProperty propertes will be skipped which have the 'auto_now' flag set to True
skip_auto_now_add: If True then all DateTimeProperty propertes will be skipped which have the 'auto_now_add' flag set to True
extra_args: Keyword arguments to override from the cloned entity and pass
to the constructor.
Returns:
A cloned, possibly modified, copy of entity e.
"""
klass = e.__class__
props = {}
for k, v in klass.properties().iteritems():
if not (type(v) == db.DateTimeProperty and ((skip_auto_now and getattr(v, 'auto_now')) or (skip_auto_now_add and getattr(v, 'auto_now_add')))):
if type(v) == db.ReferenceProperty:
value = getattr(klass, k).get_value_for_datastore(e)
else:
value = v.__get__(e, klass)
props[k] = value
props.update(extra_args)
return klass(**props)
第一个if
表达式写得不是很优雅,如果你有更好的写法,欢迎分享。
19
如果你在使用NDB,你可以很简单地通过下面的方式复制数据:
new_entity.populate(**old_entity.to_dict())
61
给你看看:
def clone_entity(e, **extra_args):
"""Clones an entity, adding or overriding constructor attributes.
The cloned entity will have exactly the same property values as the original
entity, except where overridden. By default it will have no parent entity or
key name, unless supplied.
Args:
e: The entity to clone
extra_args: Keyword arguments to override from the cloned entity and pass
to the constructor.
Returns:
A cloned, possibly modified, copy of entity e.
"""
klass = e.__class__
props = dict((k, v.__get__(e, klass)) for k, v in klass.properties().iteritems())
props.update(extra_args)
return klass(**props)
示例用法:
b = clone_entity(a)
c = clone_entity(a, key_name='foo')
d = clone_entity(a, parent=a.key().parent())
编辑:如果使用NDB的变化
结合Gus下面的评论和一个修复,针对那些指定不同数据存储名称的属性,以下代码在NDB中可以正常工作:
def clone_entity(e, **extra_args):
klass = e.__class__
props = dict((v._code_name, v.__get__(e, klass)) for v in klass._properties.itervalues() if type(v) is not ndb.ComputedProperty)
props.update(extra_args)
return klass(**props)
示例用法(注意 key_name
在NDB中变成了 id
):
b = clone_entity(a, id='new_id_here')
附带说明:可以看到 _code_name
的使用,它用来获取Python友好的属性名称。如果没有这个,像 name = ndb.StringProperty('n')
这样的属性会导致模型构造器抛出一个错误:AttributeError: type object 'foo' has no attribute 'n'
。