如何在 Ruby 中创建对象的深层副本?

Posted

技术标签:

【中文标题】如何在 Ruby 中创建对象的深层副本?【英文标题】:How to create a deep copy of an object in Ruby? 【发布时间】:2012-01-02 15:21:15 【问题描述】:

我做了一些搜索,发现了一些关于创建深拷贝操作符的不同方法和帖子。

在 Ruby 中是否有一种快速简单(内置)的方法来深度复制对象?这些字段不是数组或哈希。

在 Ruby 1.9.2 中工作。

【问题讨论】:

相关:***.com/questions/14104542/ruby-object-deep-copying 【参考方案1】:

有一个本地实现来执行 ruby​​ 对象的深度克隆:ruby_deep_clone

用 gem 安装它:

gem install ruby_deep_clone

示例用法:

require "deep_clone"
object = SomeComplexClass.new()
cloned_object = DeepClone.clone(object)

它比 Marshal 方法和事件处理冻结物体的速度快大约 6 到 7 倍。

注意这个项目不再维护了(上次提交在2017,有报道issues)

【讨论】:

不幸的是,这个 gem 不处理继承自 Array 并包含它们自己的复杂嵌套类的自定义 Collection 类。 Ruby_deep_clone 将这些对象转换为数组,它们会失去自定义属性。 根据作者的说法,这个项目不再维护,也没有准备好生产。 @bbozo 它声称支持 Ruby 2.4。是否有消息来源说它不再维护? 此答案与之前发布的***.com/questions/8206523/… 重复【参考方案2】:

我创建了一个本地实现来执行 ruby​​ 对象的深度克隆。

它比 Marshal 方法快大约 6 到 7 倍。

https://github.com/balmma/ruby-deepclone

注意这个项目不再维护了(上次提交在2017,有报道issues)

【讨论】:

根据作者的说法,这个项目不再维护,也没有准备好生产。【参考方案3】:

你真的不需要宝石。这再简单不过了,这不值得一个 Gem 的开销!

def deep_clone(obj)
  obj.clone.tap do |new_obj|
    new_obj.each do |key, val|
      new_obj[key] = deep_clone(val) if val.is_a?(Hash)
    end
  end
end

【讨论】:

【参考方案4】:

自动深度克隆并不总是您想要的。通常您需要定义一些选定的属性来进行深度克隆。一种灵活的方法是实现initialize_copyinitialize_dupinitialize_clone 方法。

如果你有课:

class Foo
  attr_accessor :a, :b
end

而您只想深度克隆:b,则覆盖initialize_* 方法:

class Foo
  attr_accessor :a, :b

  def initialize_dup(source)
    @b = @b.dup
    super
  end
end

当然,如果你想让@b 也深度克隆它自己的一些属性,你在 b 的类中也这样做。

Rails 会这样做(请参阅 https://github.com/rails/rails/blob/0951306ca5edbaec10edf3440d5ba11062a4f2e5/activemodel/lib/active_model/errors.rb#L78)

为了更完整的解释,我从这篇帖子https://aaronlasseigne.com/2014/07/20/know-ruby-clone-and-dup/学到了这里

【讨论】:

【参考方案5】:

您可以为此使用重复的 gem。

它是一个小的纯红宝石,能够递归复制对象 它也会将它的对象引用复制到新的副本中。

require 'duplicate'
duplicate('target object')

https://rubygems.org/gems/duplicate

https://github.com/adamluzsi/duplicate.rb

【讨论】:

【参考方案6】:

Rails 有一个名为deep_dup 的递归方法,它将返回对象的深层副本,并且与dupclone 相反,它甚至可以在复合对象(数组/哈希的数组/哈希)上工作。 很简单:

def deep_dup
  map  |it| it.deep_dup 
end

【讨论】:

Rails 的 Object#deep_dup 实际上是一个浅的dup 调用。 Besdie Hash 和 Array,您需要实现自己的 deep_dup 才能使其工作。【参考方案7】:

我建议您使用 ActiveSupport gem,它为您的原生 Ruby 核心添加了很多糖,而不仅仅是 deep clone 方法。

您可以查看documentation 以获取有关已添加方法的更多信息。

【讨论】:

【参考方案8】:

还可以查看 deep_dive。这允许您对对象图进行受控的深层副本。

https://rubygems.org/gems/deep_dive

【讨论】:

【参考方案9】:

原始 Ruby 中没有内置深拷贝,但您可以通过编组和解组对象来破解它:

Marshal.load(Marshal.dump(@object))

但这并不完美,并且不适用于所有对象。更稳健的方法:

class Object
  def deep_clone
    return @deep_cloning_obj if @deep_cloning
    @deep_cloning_obj = clone
    @deep_cloning_obj.instance_variables.each do |var|
      val = @deep_cloning_obj.instance_variable_get(var)
      begin
        @deep_cloning = true
        val = val.deep_clone
      rescue TypeError
        next
      ensure
        @deep_cloning = false
      end
      @deep_cloning_obj.instance_variable_set(var, val)
    end
    deep_cloning_obj = @deep_cloning_obj
    @deep_cloning_obj = nil
    deep_cloning_obj
  end
end

来源:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/43424

【讨论】:

好的,谢谢。我找到了一篇关于编组的帖子,但不知道这是否是一个合理的解决方案。 Marshal.load(Marshal.dump(@object)) 效果很好。 是的,它只是相对而言比较慢 - 只有在一次克隆大量对象时才会注意到 显然,它还假设对象首先是可编组的,但并非所有对象都是。 怎么会没有任何 ruby​​ 内置的方法呢?

以上是关于如何在 Ruby 中创建对象的深层副本?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中创建对象的副本?

如何在 PHP 中创建对象的副本?

如何在 Ruby 中创建 JSON 对象

如何制作由映射插件创建的剔除对象的深层副本

javascript 如何在JavaScript中创建对象或数组的副本?

有没有更好的方法在 C# 中创建深克隆和浅克隆?