如何在Ruby中检查一个对象是否可以迭代?

37

如何在Ruby中检查一个对象是否是可迭代的?

也就是说,我想要一个方法来干净地检查一个对象是否是可迭代的,就像这样:

def is_iterable(my_object)
  ..
end

除非在方法内部明确命名类,否则我真的不知道从哪里开始。

编辑: 对于我的目的,假设可迭代对象是可以使用 .each 方法进行操作的。


2
通常,如果你计划说“标题已经说了一切”,那么你并没有包含足够的信息。 - Andrew Grimm
6个回答

49

这个不起作用,因为Hash也是一个Enumerable,但在大多数情况下不应该被识别为集合。 - Wile E.
  1. OP并没有询问“集合”,而是关于“如何在Ruby中检查对象是否可迭代?”(字面上的问题标题)。在Ruby中,成为Enumberable基本上就是这个定义。
  2. Java将其HashMap称为集合,.Net的Dictionary定义在System.Collections.Generic中,因此断言它们不被认为是“集合”似乎是武断的。
- Michael Kohl
请参阅https://en.wikipedia.org/wiki/Collection_(abstract_data_type)中的第2.3节“关联数组”,这正是Ruby Hash的定义。 - Michael Kohl

48

就我使用的情况而言,可迭代对象是指您可以对其执行.each操作的东西。

您只需询问此对象是否具有此方法即可。

def iterable?(object)
  object.respond_to?(:each)
end

9
并非所有情况下都适用。nil..nil 是一个范围对象,可以响应each方法,但无法迭代,当尝试进行迭代时会抛出TypeError: can't iterate from NilClass异常。我在一个日期范围输入项目中遇到了这个问题,测试其是否与无效的用户输入(如空值或随机字符串等)相匹配。 - Vance Lucas
其他对象可能有方法#each,更好的答案(如下)是查看它是否是可枚举的。 - saneshark
1
@saneshark:在一种鸭子类型的语言中进行类型测试?我不这么认为,这不符合惯用法。 - Sergio Tulentsev

10

根据您的整体目标和需要对结果进行的操作,有许多方法可以实现这一点。

  1. If you just want to use duck typing to see if the object responds to #each, then you can just ask the object if it has such a method.

    my_object.respond_to? :each
    
  2. If you want to find out if the object mixes in the Enumerable class, you can check the class for inclusion.

    my_object.class.include? Enumerable
    
  3. If you want a list of all the ancestors and mixins, you want the #ancestors method. For example, to see whether my_object inherits from the Enumerable class, you could invoke:

    my_object = []
    my_object.class.ancestors
    => [Array, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]
    
    my_object.class.ancestors.include? Enumerable
    => true
    

1
为什么要使用 my_object.class.include? Enumerable 而不是 my_object.is_a? Enumerable - dbenhur
@dbenhur 语义清晰。结果通常是相同的。 - Todd A. Jacobs
你可以这样检查,而不是查看祖先:my_object.class < Enumerator - mbillard
这不起作用,因为Hash也是一个Enumerable,但在大多数情况下不应被识别为集合。 - Wile E.

2

由于Range即使不可迭代也会响应each,因此您需要特别检查Range的元素是否响应succ

def iterable?(object)
  return object.begin.respond_to? :succ if object.kind_of? Range
  object.respond_to? :each
end

1
通常情况下,您可以检查是否定义了each方法,或者对象的类中是否包含Enumerable模块:
my_object.class.include? Enumerable

0

Minitest风格的测试用例,用于ActiveRecord组合对象:

def test_iterates_over_invoice_items
  invoice = Invoice.new(items: [InvoiceItem.new(description: 'test')])

  iteration_count = 0
  invoice.each do |invoice_item|
    assert_equal 'test', invoice_item.description
    iteration_count += 1
  end

  assert_equal 1, iteration_count
end

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