如何将一个Python对象的所有属性复制到另一个对象?

12 投票
3 回答
11347 浏览
提问于 2025-04-28 13:42

我有两个类,其中一个是从另一个类继承过来的:

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 个回答

0

我觉得一个不错的选择是使用父类的属性字典(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 ... 来过滤掉不需要的属性。

0

如果你有一个父类和多个子类,并且希望子类在父类变化时自动更新,那么可以把父类的属性设置为类属性,这样子类就会自动跟着父类的变化。

如果你有多个父类,并且至少有一些父类会有子类,那么你有三种选择:

  • 使用委托(让子类去父类那里查找它不知道的东西)
  • 使用复制(这就是你现在在做的事情)
  • 使用 metaclass(元类)为每个不同的父类创建一个新的父类,然后每个父类的属性都作为类属性。
14

显而易见的解决办法是使用组合/委托,而不是继承:

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,可能还有更好的做法……

撰写回答