Why does “instance.send(:initialize, *args, **kwargs, &block)” fail only from within Class#new?

我怕爱的太早我们不能终老 提交于 2019-12-25 03:32:52

问题


I've been stuck on this for quite a while now. Take a look at this:

class SuperClass
  def self.new(*args, **kwargs, &block)
    i = allocate()

    # Extra instance setup code here

    i.send(:initialize, *args, **kwargs, &block)
    return i
  end
end

class Test < SuperClass
  def initialize
    puts "No args here"
  end
end

The class SuperClass basically "reimplements" the default new method so that some extra initialization can happen before initialize.

Now, the following works just fine:

t = Test.allocate
t.send(:initialize, *[], **{}, &nil)

However, this does not:

t = Test.new
ArgumentError: wrong number of arguments (1 for 0)
from (pry):7:in `initialize'

It fails on this line in SuperClass:

i.send(:initialize, *args, **kwargs, &block)

But apparently it only fails if called within the new method. I have confirmed that args == [], kwargs == {} and block == nil.

Is anybody able to explain this?


Ruby version:

ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-linux]

Please refrain from suggesting that I don't overload Class.new. I am aware I can use Class.inherited and Class.append for the same result. This question is only about why the call to initialize fails.


回答1:


Let's examine a simpler example, especially because the problem isn't as specific as the question and its title make it look like but see for yourself.

def m   # takes no arguments
end
m(**{}) # no argument is passed
h = {}
m(**h)  # an argument is passed => ArgumentError is raised

This inconsistency was introduced in 2.2.1 by a commit intended to fix a segmentation fault involving **{} (Bug #10719). The commit special-cases **{} to not pass an argument. Other ways like **Hash.new and h={};**h still pass an empty hash as argument.

Previous versions consistently raise ArgumentError (demo). I could be wrong but I believe that's the intended behavior. However it may or may not be the one actually wants. So if you think double-splatting an empty hash shouldn't pass an argument (like **{} at the moment) and therefore work similar to splatting an empty array, there is an open issue about that (Bug #10856). It also mentions this relatively new inconsistency.




回答2:


A simple *args will capture all arguments including keyword arguments, in case you don't need to reference kwargs separately in the new method:

class SuperClass
  def self.new(*args, &block)
    i = allocate

    # Extra instance setup code here

    i.send(:initialize, *args, &block)
    i
  end
end


来源:https://stackoverflow.com/questions/33096134/why-does-instance-sendinitialize-args-kwargs-block-fail-only-from-wi

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!