为什么open("url")有时返回File,有时返回StringIO?

8

我在S3上有两个CSV文件。当我打开其中一个时,返回一个文件对象。而当我打开另一个时,则返回一个StringIO对象

fn1 #=> "http://SOMEWHERE.s3.amazonaws.com/setup_data/d1/file1.csv" 
open(fn1) #=> #<File:/var/folders/sm/k7kyd0ns4k9bhfy7yqpjl2mh0000gn/T/open-uri20140814-26070-11cyjn1> 

fn2 #=> "http://SOMEWHERE.s3.amazonaws.com/setup_data/d2/d3/file2.csv" 
open(fn2) #=> #<StringIO:0x007f9718670ff0> 

为什么?有没有一种方法可以使用一致的数据类型打开它们?
我需要将相同的数据类型 String 传递到 CSV.read(open(file_url)),但如果有时它获取到的是 File,有时是 StringIO,那就行不通了。
它们是通过不同的 Ruby 脚本创建的(它们包含非常不同的数据)。
在我的 Mac 上,它们都似乎是普通文本 CSV 文件,并且它们是通过 AWS 控制台上传的,并且具有相同的权限和相同的元数据(content-type: application/octet-stream)。
2个回答

6
这是有意设计的。如果对象的大小大于10240字节,则会创建临时文件。来自source的说明:
StringMax = 10240
def <<(str)
  @io << str
  @size += str.length
  if StringIO === @io && StringMax < @size
    require 'tempfile'
    io = Tempfile.new('open-uri')
    io.binmode
    Meta.init io, @io if Meta === @io
    io << @io.string
    @io = io
  end
end

如果需要一个StringIO对象,你可以使用fastercsv

1
有没有一种方法可以使用一致的数据类型打开它们?我目前正在使用CSV.read(open(file_url)),根据您的解释,如果文件很大,则可以工作,如果文件很小,则会失败。 - jpw

1

CSV::read接受一个文件路径作为参数,而不是已经打开的IO对象。它将打开文件并读取内容。你的代码对于Tempfile情况可以工作,因为Ruby在任何传递给File::open的东西后面调用to_path方法,并且Files响应这个方法。发生的事情是CSV在同一个文件上打开了另一个IO。

与其使用CSV::read,你可以创建一个新的CSV对象并在其中调用read(实例方法而非类方法)。 CSV:new正确处理IO对象:

CSV.new(open(file_url)).read

非常好的解释和答案,谢谢。我在相同的两个文件URL上进行了测试,完美地工作了。 - jpw

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