Python中的复制构造函数?

123

Python中有拷贝构造函数吗?如果没有,我该怎么做才能实现类似的功能?

我的情况是,我正在使用一个库,我已经通过添加额外的功能扩展了其中的一个类,并且我希望能够将从库中获取的对象转换为我自己类的实例。


你可能会对这个问题感兴趣[免责声明:我是提问者]:https://dev59.com/RnNA5IYBdhLWcg3wX8nk - balpha
小心。一些回答者发布的警告不容小觑。 - balpha
看起来不太易读,我可能会将我的代码从使用继承更改为仅封装其他对象。 - Zitrax
8个回答

84

我认为你想要使用copy模块

import copy

x = copy.copy(y)        # make a shallow copy of y
x = copy.deepcopy(y)    # make a deep copy of y

您可以像控制pickle一样来控制复制。


14
deepcopy 可以很好地在类定义之外执行此操作。但是 @Zitrax 希望在类定义内部完成此操作,以便新实例从不同(父)类型(类)的对象继承属性(数据)。 - hobs

43

在Python中,可以使用默认参数定义复制构造函数。假设你想让普通构造函数运行函数non_copy_constructor(self),并让复制构造函数运行copy_constructor(self, orig)。那么你可以这样做:

class Foo:
    def __init__(self, orig=None):
        if orig is None:
            self.non_copy_constructor()
        else:
            self.copy_constructor(orig)
    def non_copy_constructor(self):
        # do the non-copy constructor stuff
    def copy_constructor(self, orig):
        # do the copy constructor

a=Foo()  # this will call the non-copy constructor
b=Foo(a) # this will call the copy constructor

26

我通常实现复制构造函数的一个简单示例:

import copy

class Foo:

  def __init__(self, data):
    self._data = data

  @classmethod
  def from_foo(cls, class_instance):
    data = copy.deepcopy(class_instance._data) # if deepcopy is necessary
    return cls(data)

2
不错。这将像Foo.from_foo(foo)一样工作。更进一步的改进是使其适用于Foo(foo),这可能是OP @Zitrax想要的。 - hobs
2
我不喜欢这个的原因是:1.它强制__init__data作为输入,并且对于更多类型不够灵活。2.它不允许重载。3.__init__是公共的,如果你有其他构造函数类型,你可能不想要它。 - Gulzar

14

针对您的情况,我建议编写一个类方法(或可以是静态方法或单独函数),它将以库类的实例为参数,并返回一个已复制所有适用属性的您自己的类实例。


2
通过迭代__dict__来实现吗? - hobs

8
建立在 @Godsmith 的 思路 基础上,同时解决 @Zitrax 的需求(我想是)在构造函数中为所有属性进行数据复制。
class ConfusionMatrix(pd.DataFrame):
    def __init__(self, df, *args, **kwargs):
        try:
            # Check if `df` looks like a `ConfusionMatrix`
            # Could check `isinstance(df, ConfusionMatrix)`
            # But might miss some "ConfusionMatrix-elligible" `DataFrame`s
            assert((df.columns == df.index).all())
            assert(df.values.dtype == int)
            self.construct_copy(df, *args, **kwargs)
            return
        except (AssertionError, AttributeError, ValueError):
            pass
        # df is just data, so continue with normal constructor here ...

    def construct_copy(self, other, *args, **kwargs):
        # construct a parent DataFrame instance
        parent_type = super(ConfusionMatrix, self)
        parent_type.__init__(other)
        for k, v in other.__dict__.iteritems():
            if hasattr(parent_type, k) and hasattr(self, k) and getattr(parent_type, k) == getattr(self, k):
                continue
            setattr(self, k, deepcopy(v))

这个 ConfusionMatrix 类继承了 pandas.DataFrame,并添加了许多其他属性和方法,除非可以复制 other 矩阵数据,否则需要重新计算。我发现这个问题是通过搜索解决方案找到的。

2
请注意,这种方法与许多其他语言的复制构造函数不同,它不会调用属性的复制构造函数。 - TLW
同时请注意,如果v是一个numpy数组或导出的对象,通过使用==进行比较会引发错误: ValueError: 无法确定具有多个元素的数组的真值。请使用a.any()或a.all() 。如果您有可能是numpy数组样式的属性,则非失败性比较(也适用于常规属性,如ints、strs等)为 np.array_equal - ntjess

3

我有一个类似的情况,不同之处在于新类只需要复制属性。因此,结合@Dunham的想法并加入一些具体性到@meisterluk的建议中,@meisterluk的“copy_constructor”方法可能是:

from copy import deepcopy
class Foo(object):
    def __init__(self, myOne=1, other=None):
    self.two = 2
    if other <> None:
        assert isinstance(other, Foo), "can only copy instances of Foo"
        self.__dict__ = deepcopy(other.__dict__)
    self.one = myOne

def __repr__(self):
    out = ''
    for k,v in self.__dict__.items():
        out += '{:>4s}: {}, {}\n'.format(k,v.__class__,v)
    return out

def bar(self):
    pass

foo1 = Foo()
foo2 = Foo('one', foo1)

print '\nfoo1\n',foo1
print '\nfoo2\n',foo2

输出结果:
foo1
 two: <type 'int'>, 2
 one: <type 'int'>, 1


foo2
 two: <type 'int'>, 2
 one: <type 'str'>, one

2
以下解决方案可能会在简单形式上重复一些先前的解决方案。我不知道它是否符合Python规范,但它可以工作,并且在我使用它的特定情况下非常方便。
class Entity:
    def __init__(self, code=None, name=None, attrs=None):
        self.code = code
        self.name = name
        self.attrs = {} if attrs is None else attrs


    def copy(self, attrs=None):
        new_attrs = {k: v.copy() for k, v in self.attrs.items()} if attrs is None else attrs
        return Entity(code=self.code, name=self.name, attrs=new_attrs)

使用方法:

new_entity = entity.copy()

这是一个更复杂的版本,允许干预复制过程。我只在一个地方使用了它。另外请注意,在self.attrs中包含的对象也具有这种“复制构造函数”。
这个解决方案不是通用的,但非常简单,并提供了相当多的控制。

0

您可以通过以下代码实现,而无需使用任何复制模块。 Python不支持方法重载, 因此我们无法创建复制构造函数。

class student():
    name: str
    age: int

    def __init__(self, other=None):
        if other != None and isinstance(other, student):
            self.name = other.name
            self.age = other.age
        elif not(isinstance(other,student)) and other!=None:
            raise TypeError
    def printInfo(s):
        print(s.name, s.age)

这并没有扩展基类。从复制构造函数初始化对象意味着您可以将类A的子类B从类A的实例中初始化。在这种情况下,类B将拥有A的所有方法/属性以及自己的方法/属性,并将从现有的A实例初始化。 - V13
这个例子仅用于复制,而不是深度复制。在复制中,我们只复制对象的属性,而不是整个对象。 - Mehul Talpada

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接