Как проверить, действительно ли переменная отвечает на: dup?
Я хотел использовать value.respond_to?(:dup) ? value.dup : value
, чтобы проверить, могу ли я дублировать объект, но он не удался с TypeError
на booleans, nil или таких "примитивах".
Я закончил с:
begin
value = value.dup
rescue
#ignore, use the original if no dup-able (e.g nil, true, etc)
end
Есть ли лучший способ?
Бонус: почему он отвечает :dup
?
Не глубоко dup
, просто для вопроса.
EDIT: Мысли:
-
obj.class.methods.include? :new
приятный, но слишком хакерский, я думаю, что он имеет плохую производительность.
-
Marshal
также выглядит как overkill
- спасение одной строки могло быть лучшим решением, но на данный момент невозможно создать однострочное спасение типа (IIUC matz на этом!), и как @JörgWMittag упомянул о своей ошибке.
- Лично я думаю, что
dup
определяется на уровне объекта неправильно.
Итак, цитируя @Linuxios
На самом деле нет лучшего способа
Ответы
Ответ 1
На самом деле нет лучшего способа. dup
определяется в Object, что означает, что любой класс, который хочет не отвечать на него, должен перегрузить его, чтобы создать исключение. NilClass
, TrueClass
, FalseClass
и Number
- все подклассы объекта. Это означает, что они должны переопределить метод, чтобы вызвать ошибку.
В любом случае, если вы ищете глубокую копию, это использовать обычный Marshal.load(Marshal.dump(obj))
, который будет обрабатывать номера, bools и nil просто отлично.
Например:
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
Ответ 2
Вы можете написать это как одну строку следующим образом:
value = value.dup rescue value
Очень ясно.
Стандартно определить метод dup
, который вызывает TypeError
для типов, которые не могут быть дублированы. Таким образом, любой объект будет "реагировать" на него. Вам действительно нужно позвонить и проверить с началом-спасением.
Ответ 3
Я думаю, что причина, по которой он отвечает на dup
, состоит в том, что класс наследует объект Object, который имеет метод 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;
}
Я думаю, единственное, что вы могли бы сделать, это проверить эти специальные константы в вашем методе.
Ответ 4
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
Ответ 5
Есть несколько лучший способ, не уверенный в проверке сообщения об ошибке:
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