问题
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