Ruby:为什么传递给send()的输入被包装在Hash中?
我在 Ruby 中使用 send() 方法来调用我定义的方法。该方法采用OpenStruct.
这是一个片段,显示了我如何使用send以下方法调用我的方法:
my_open_struct = OpenStruct.new(foo: "foo")
result = @my_object.send(
:my_method_that_takes_an_openstruct,
name_of_openstruct_param: my_open_struct)
的问题是,内部my_method_that_takes_an_openstruct的OpenStruct参数是得到包裹在一个Hash,从而导致在记录这样的输出:
Just before calling send #<OpenStruct foo="foo">
Inside my_method_that_takes_an_openstruct: {:name_of_openstruct_param=>#<OpenStruct foo="foo">}
为什么会发生这种情况,我该如何防止这种包装行为?
回答
我假设my_method看起来像这样
class Foo
def my_method(arg)
puts "#{arg}"
end
end
如果你从一个Python背景的,你可能会认为我们可以称之为my_method是两种foo.my_method(1)或foo.my_method(arg: 1)。但这不是它在 Ruby 中的工作方式。在 Ruby 中,参数要么是命名的,要么是定位的。为了命名一个参数,我们在它后面加上一个冒号。
class Foo
def my_method(arg:)
puts "#{arg}"
end
end
现在,我们可以做的Foo.new.my_method(arg: 1)或Foo.new.send(:my_method, arg: 1),但它是不正确做Foo.new.my_method(1)。
您获得散列的原因是兼容性技巧。在旧版本的 Ruby 中,在我们命名参数之前,约定是在末尾采用单个哈希参数
def foo(a, b, opts)
...
end
然后以下两个调用将是等效的(前者是后者的语法糖)
foo(1, 2, foo: "bar", bar: "baz")
foo(1, 2, {foo: "bar", bar: "baz"})
基本上,如果任何命名参数出现在参数列表中,它们将被转换为单个散列并作为最终参数传递给函数。
此行为自 Ruby 2.7 起已弃用,并在 Ruby 3.0 中删除。现在正确的约定是采用显式命名参数,并且最新版本的 Ruby 支持双 splat**运算符,用于将散列转换为命名参数,类似于具有相同名称的 Python 运算符。