Why does “instance.send(:initialize, *args, **kwar

2019-09-03 09:51发布

问题:

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


标签: ruby ruby-2.2