使用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(显然,除了用户和键之外)。 所以很明显,这段代码正在做一些事情,因为它将用Nonereplace默认值(所有属性都设置了默认值),但根本不是我想要的。 build议?
干得好:
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'
会导致模型构造函数引发一个AttributeError: type object 'foo' has no attribute 'n'
。
如果你使用的NDB,你可以简单地复制: new_entity.populate(**old_entity.to_dict())
这只是尼克·约翰逊的优秀代码的延伸,以解决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)
第一个expression式不是很优雅,所以我很感激,如果你能分享一个更好的方式来写它。
我既不是Python也不是AppEngine guru,但是不能dynamic获取/设置属性?
props = {} for p in Thing.properties(): props[p] = getattr(old_thing, p) new_thing = Thing(**props).put()
在尼克的答案灵感的变化,处理的情况下,您的实体具有(重复)StructuredProperty,其中StructuredProperty本身有ComputedProperties。 不知何故,它可以用字典理解来更加简洁地写出来,但是这里是为我工作的更长的版本:
def removeComputedProps(klass,oldDicc): dicc = {} for key,propertType in klass._properties.iteritems(): if type(propertType) is ndb.StructuredProperty: purged = [] for item in oldDicc[key]: purged.append(removeComputedProps(propertType._modelclass,item)) dicc[key]=purged else: if type(propertType) is not ndb.ComputedProperty: dicc[key] = oldDicc[key] return dicc def cloneEntity(entity): oldDicc = entity.to_dict() klass = entity.__class__ dicc = removeComputedProps(klass,oldDicc) return klass(**dicc)
如果您已经为属性重新命名了底层的密钥,那么这可能会非常棘手……有些人select这样做,而不是进行海量数据更改
说你从这开始:
class Person(ndb.Model): fname = ndb.StringProperty() lname = ndb.StringProperty()
那么有一天你真的认为使用first_name和last_name会更好,所以你这样做:
class Person(ndb.Model): first_name = ndb.StringProperty(name="fname") last_name = ndb.StringProperty(name="lname")
现在当你做Person._properties(或.properties()或person_instance._properties)时,你将得到一个字典,其中包含与底层名称(fname和lname)匹配的键,但不会匹配类上的实际属性名称…所以如果你把它们放到新实例的构造函数中,或者使用.populate()方法(上面的例子将会中断)
无论如何,在NDB中,模型的实例具有._values字典,该字典由基础属性名称来键入…并且您可以直接更新它。 我结束了这样的事情:
def clone(entity, **extra_args): klass = entity.__class__ clone = klass(**extra_args) original_values = dict((k,v) for k,v in entity._values.iteritems() if k not in clone._values) clone._values.update(original_values) return clone
这不是最安全的方法,因为还有其他的辅助方法可以做更多的工作(比如通过使用_store_value()和_retrieve_value()来validation和转换计算属性),但是如果你是模型够简单,而且你喜欢生活在边缘:)
以下是@zengabor提供的代码 ,其中if
expression式的格式更易于阅读。 它可能不符合PEP-8:
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)