2014-01-06 31 views
15

我想使用value.respond_to?(:dup) ? value.dup : value來檢查是否可以複製一個對象,但是在布爾值,無或類似的「原語」上使用TypeError失敗。如何檢查變量是否真的響應__:dup?

我已經結束了:

begin 
    value = value.dup 
rescue 
    #ignore, use the original if no dup-able (e.g nil, true, etc) 
end 

有沒有更好的辦法?

獎勵:它爲什麼迴應:dup

不深dup,只是爲了這個問題。

編輯:思考:

  • obj.class.methods.include? :new是好的,但有點太hackish的,我認爲它也有壞的表現
  • Marshal也貌似矯枉過正
  • 一行救援本來是最好的解決方案,但類型特定的一線救援目前是不可能的(IIUC matz is on that!),並且@JörgWMittag提到了它的錯誤。
  • 我個人認爲dup被定義在對象級別是錯誤的。

所以,引用@Linuxios

確實沒有更好的辦法

+0

什麼是Ruby版本使用的是?我在1.9.3上,而'value.respond_to?(:dup)'這裏的值是布爾值,向我返回'false'而不是'類型錯誤'。與nils和其他人類似。 – Nerve

+0

@Nerve 1.9.3-p484 ... –

+0

FWIW這方面有一個懸而未決的問題,但它沒有得到太多的關注:https://bugs.ruby-lang.org/issues/11929 –

回答

5

確實沒有更好的辦法。 dup定義在Object上,這意味着任何想要不響應它的類都需要重載它來拋出異常。 NilClass,TrueClass,FalseClassNumber都是Object的子類。這意味着他們必須重寫該方法來拋出錯誤。

解決這個問題的一個辦法是,如果你正在尋找一個深拷貝,那就是使用通常的Marshal.load(Marshal.dump(obj)),它會處理數字,布爾和零。

例如:

1.9.3-p392 :001 > obj = "hi" 
=> "hi" 
1.9.3-p392 :002 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id 
=> true 
1.9.3-p392 :003 > obj = 3 
=> 3 
1.9.3-p392 :004 > Marshal.load(Marshal.dump(obj)).object_id != obj.object_id 
=> false 
+1

和'Marshal.load (Marshal.dump(obj))。object_id!= obj.object_id'告訴你它是否是dup。 –

+2

@CarySwoveland:是的,這是一個真正的深層複製,除非'obj'是一個數字,bool或零。 – Linuxios

+0

使用元帥+1。這是「hack-ish」,但不是黑客。 –

1

我認爲它響應dup的原因在於,從類具有dup方法對象繼承。

看來,在對象檢查dup方法爲「特殊常量」,並引發錯誤,你看到:

VALUE 
rb_obj_dup(VALUE obj) 
{ 
    VALUE dup; 

    if (rb_special_const_p(obj)) { 
     rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj)); 
    } 
    dup = rb_obj_alloc(rb_obj_class(obj)); 
    init_copy(dup, obj); 
    rb_funcall(dup, id_init_dup, 1, obj); 

    return dup; 
} 

我想你可以做的唯一的事情就是檢查這些特殊常量在你的方法中。

6

你可以把它寫成一行是這樣的:

value = value.dup rescue value 

很清楚。

定義dup方法的標準是爲不能複製的類型提出TypeError。因此任何物體都會「迴應」它。你真的必須打電話給它,並檢查一下開始救援結束。

+0

+1我忘了所有關於「救援」的一行。 – Linuxios

+6

你應該*永遠不要*從'StandardError'中盲目'救援'。你應該只處理最具體的異常,在這種情況下'TypeError'。 –

1
def dupable?(obj) 
    obj.class.methods.include? :new 
end 

dupable?(1)  # => false 
dupable?(3.2) # => false 
dupable?(:a) # => false 
dupable?(true) # => false 
dupable?(nil) # => false 
dupable?("cat") # => true 
+0

哇。創意。我想知道,它是否涵蓋所有情況? –

1

有一個稍微好辦法,不知道雖然檢查錯誤消息:

begin 
    value = value.dup 
rescue TypeError => e 
    # !!! not sure about the following line 
    raise unless e.message == "can't dup %s" % value.class.name 
    #ignore, use the original if no dup-able (e.g nil, true, etc) 
end