【问题标题】:Method named `hash` in main module overrides some object's `hash` method主模块中名为 `hash` 的方法会覆盖某些对象的 `hash` 方法
【发布时间】:2018-05-28 22:44:03
【问题描述】:

给定这个脚本

def hash
  puts "why?"
end

x = {}
x[[1,2]] = 42

输出如下

why?
/tmp/a.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
    from /tmp/a.rb:6:in `<main>'

在这种情况下,脚本中定义的hash 函数似乎覆盖了Array#hash。由于我的hash 方法的返回值是nil 而不是Integer,因此它会引发异常。以下脚本似乎证实了这一点

puts [1,2,3].hash

def hash
  puts "why?"
end

puts [1,2,3].hash

输出是

-4165381473644269435
why?
/tmp/b.rb:6:in `hash': no implicit conversion of nil into Integer (TypeError)
    from /tmp/b.rb:6:in `<main>'

我尝试查看 Ruby 源代码,但无法弄清楚为什么会发生这种情况。是否记录了这种行为?

【问题讨论】:

  • 我没有得到这种行为。 class Array; def hash; nil; end; end; puts [1,2,3].hash 打印一个空行并为我返回 nil。 x[[1,2]] = 1 仍然会引发 TypeError,但 x[nil] = 1 工作正常。确实很奇怪的行为..你在用什么红宝石版本?我正在使用 2.3.1

标签: ruby


【解决方案1】:

您没有覆盖Array#hash,而是通过创建Object#hash 来影响Kernel#hash

puts method(:hash)
def hash   
  puts "why?"
end
puts method(:hash)

打印出来的:

#<Method: Object(Kernel)#hash>
#<Method: Object#hash>

修复它以便我们可以看到更多:

def hash
  puts "why?"
  super
end

x = {}
x[[1,2]] = 42

现在的输出是:

why?
why?

而且没有错误。尝试使用x[[1,2,3,4,5,6,7]] = 42,您将看到why? 打印 次。每个数组元素一次,因为数组的哈希方法使用其元素的哈希值。并且Integer#hash 不存在,它从Object/Kernel 继承了它的hash 方法,所以你的被使用了。

【讨论】:

  • 在我的系统上,第二个method(:hash) 实际上会使解释器崩溃。也许 Ruby 开发人员应该在定义顶级哈希时发出警告......
  • 你在哪里/如何尝试的?在 IRB 左右,还是您将其保存在脚本文件中并运行?
  • 只有在 IRB 中才会崩溃。但是重新定义Object#hash 似乎仍然是一件值得警告的事情。我看不出这是个好主意。
  • 有趣。我不认为主脚本中的方法可以在没有明确指定的情况下影响 Kernel 方法。
  • @Becojo 在此之前也不知道 :-)。我认为Chuck's answer about the main object的第一段解释得很好。
【解决方案2】:

这是由于 Ruby 顶级中的一种 hack。你有没有想过这是如何工作的?

def foo
end
p self
foo

class Bar
  def test
    p self
    foo
  end
end

Bar.new.test # no error

两个完全不同的对象(main 和一个Bar)如何像调用私有方法一样调用foo?原因是因为……是私有方法调用。

当您在 Ruby 脚本的顶层定义方法时,它会(通过Object)包含在每个对象中。这就是为什么您可以像调用全局函数一样调用顶级方法。

但是为什么这只会破坏hash 而不是其他常用方法呢?例如,def to_s;end 不会破坏 to_s。原因是因为hash 是递归的:大多数* 类实现最终都会调用Object#hash 来实现它们。通过重新定义该基本情况,您可以在全球范围内打破它。对于像 to_s 这样的其他方法,您不会看到全局更改,因为它位于继承链的上游并且不会被调用。

* 唯一不会破坏的对象是一些可能具有硬编码哈希值的文字,例如[]{}""true

【讨论】:

  • 嗯,x[[[], {}, ""]] = 42x[true] = 42 不会导致错误,但 x[[true]] = 42 会。我还不能解释...
  • 我可能找到了罪魁祸首。从line 666 :-) 开始。看起来x[true] 使用RHASH 来获取true 的哈希值。它被定义为in internal.h,所以我猜它确实使用了一些低级别的内部而不是我们的hash 方法。但是Array#hashuses rb_hash,显然确实使用了我们的高级hash方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-10
  • 2016-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-25
相关资源
最近更新 更多