如何在 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_copy
、initialize_dup
和initialize_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
的递归方法,它将返回对象的深层副本,并且与dup
和clone
相反,它甚至可以在复合对象(数组/哈希的数组/哈希)上工作。
很简单:
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 中创建对象的深层副本?的主要内容,如果未能解决你的问题,请参考以下文章