如何将一个Python对象的所有属性复制到另一个对象?
我有两个类,其中一个是从另一个类继承过来的:
class DaParent(object):
name = ''
number = 0
class DaChild(DaParent):
additional = ''
现在我创建了一个父类,并修改了一些属性:
parent = DaParent()
parent.name = 'papa'
parent.number = 123
从这个时候开始,我想创建一个子类,并希望把父类的所有属性都复制过来。当然,我可以这样做:
child = DaChild()
child.name = parent.name
child.number = parent.number
问题是,随着开发的进行,这个类会有很多属性,我不想一直手动复制这些属性到子类里。
有没有办法可以自动把父类的属性转移到新的子类对象中?欢迎任何建议!
[编辑]
我想解释一下我为什么想这样做。我使用的是Peewee ORM来和我的数据库互动。现在我想对一个表进行版本控制(也就是说,如果记录被更新,我想保留所有之前的版本)。我打算通过创建一个Person
类和一个继承自Person
类的PersonRevision
类来实现这个目标。然后我会重写peewee的save()方法,不仅保存Person
对象,还把所有属性复制到PersonRevision
对象中,并保存这个对象。因为我实际上不会直接使用PersonRevision
类,所以我不需要任何复杂的东西。我只想复制属性,然后调用这个对象的save()
方法。
3 个回答
我觉得一个不错的选择是使用父类的属性字典(parent.__dict__
)来填充子类的属性字典(child.__dict__
)。你需要一个 @classmethod
来从特定的父类创建一个子类。下面的代码示例展示了如何在你的例子中做到这一点:
class DaParent(object):
name = ''
number = 0
class DaChild(DaParent):
additional = ''
@classmethod
def from_parent(cls, parent):
child = cls()
for attribute_key in parent.__dict__.keys():
child.__dict__[attribute_key] = parent.__dict__[attribute_key]
return child
if __name__ == '__main__':
parent = DaParent()
parent.name = 'papa'
parent.number = 123
child = DaChild.from_parent(parent)
print(child.name)
print(child.number)
进一步解释:
@classmethod
类方法是一种静态方法,也就是说:它应该从类本身调用(DaChild.from_parent
),而不是从类的实例调用(child.from_parent
)。
它的第一个参数是 cls
,就像 __init__
中的 self
,可以用作构造函数。在这个例子中,child = cls()
就是这样用的。
填充字典
Python 对象的属性可以通过它的字典来访问和修改(比如 child.__dict__[attribute_key] = parent.__dict__[attribute_key]
这一部分)。
我遍历了父类的所有属性键。注意,你可以修改这个循环,只填充你想要的属性,使用 if ... else ...
来过滤掉不需要的属性。
如果你有一个父类和多个子类,并且希望子类在父类变化时自动更新,那么可以把父类的属性设置为类属性,这样子类就会自动跟着父类的变化。
如果你有多个父类,并且至少有一些父类会有子类,那么你有三种选择:
- 使用委托(让子类去父类那里查找它不知道的东西)
- 使用复制(这就是你现在在做的事情)
- 使用 metaclass(元类)为每个不同的父类创建一个新的父类,然后每个父类的属性都作为类属性。
显而易见的解决办法是使用组合/委托,而不是继承:
class Parent(object):
def __init__(self, name, number):
self.name = name
self.number = number
class Child(object):
def __init__(self, parent, other):
self.parent = parent
self.other = other
def __getattr__(self, name):
try:
return getattr(self.parent, name)
except AttributeError, e:
raise AttributeError("Child' object has no attribute '%s'" % name)
p = Parent("Foo", 42)
c = Child(p, "parrot")
print c.name, c.number, c.other
p.name = "Bar"
print c.name, c.number, c.other
当然,这里假设你并不想要“复制”,而是想要“引用”。如果你真的想要一个复制品,也是可以做到的,但对于可变类型来说,这可能会变得有点复杂:
import copy
class Parent(object):
def __init__(self, name, number):
self.name = name
self.number = number
class Child(object):
def __init__(self, parent, other):
# only copy instance attributes from parents
# and make a deepcopy to avoid unwanted side-effects
for k, v in parent.__dict__.items():
self.__dict__[k] = copy.deepcopy(v)
self.other = other
如果这些解决方案都不符合你的需求,请解释一下你的真实使用场景——你可能遇到了XY问题。
[编辑] 确实有点像XY问题。真正的问题是:“我该如何将一个peewee.Model
的字段复制到另一个peewee.Model
中?”peewee
使用描述符(peewee.FieldDescriptor
)来控制对模型字段的访问,并将字段名称和定义存储在模型的_meta.fields
字典中,所以最简单的解决办法是遍历源模型的_meta.fields
键,然后使用getattr
/ setattr
:
class RevisionMixin(object):
@classmethod
def copy(cls, source, **kw):
instance = cls(**kw)
for name in source._meta.fields:
value = getattr(source, name)
setattr(instance, name, value)
return instance
class Person(peewee.Model):
# fields defintions here
class PersonRevision(Person, RevisionMixin):
# additional fields definitions here
p = Person(name="foo", number=42)
r = PersonRevision.copy(p, whatelse="parrot")
注意:这段代码未经测试,我从未使用过peewee
,可能还有更好的做法……