Ruby – 关键字参数 – 您可以将所有关键字参数视为哈希吗? 怎么样?

我有一个看起来像这样的方法:

def method(:name => nil, :color => nil, shoe_size => nil) SomeOtherObject.some_other_method(THE HASH THAT THOSE KEYWORD ARGUMENTS WOULD MAKE) end 

对于任何给定的调用,我可以接受任意值的任意组合。 我喜欢命名参数,因为我可以查看方法的签名以查看可用的选项。

我不知道的是,上面的代码示例中是否有我用大写字母描述的快捷方式。

回到过去,它曾经是:

 def method(opts) SomeOtherObject.some_other_method(opts) end 

优雅,简单,几乎作弊。

这些关键字参数是否有快捷方式,还是我必须在方法调用中重构我的选项哈希?

是的,这可能的,但它不是很优雅。

您将不得不使用parameters方法 ,该方法返回方法参数及其类型的数组(在本例中,我们只有关键字参数)。

 def foo(one: 1, two: 2, three: 3) method(__method__).parameters end #=> [[:key, :one], [:key, :two], [:key, :three]] 

知道了,有多种方法可以使用该数组来获取所有参数及其提供值的哈希值。

 def foo(one: 1, two: 2, three: 3) params = method(__method__).parameters.map(&:last) opts = params.map { |p| [p, eval(p.to_s)] }.to_h end #=> {:one=>1, :two=>2, :three=>3} 

所以你的例子看起来像

 def method(name: nil, color: nil, shoe_size: nil) opts = method(__method__).parameters.map(&:last).map { |p| [p, eval(p.to_s)] }.to_h SomeOtherObject.some_other_method(opts) end 

仔细考虑使用它。 它很聪明但是以可读性为代价,其他人阅读你的代码却不喜欢它。

您可以使用辅助方法使其更具可读性。

 def params # Returns the parameters of the caller method. caller_method = caller_locations(length=1).first.label method(caller_method).parameters end def method(name: nil, color: nil, shoe_size: nil) opts = params.map { |p| [p, eval(p.to_s)] }.to_h SomeOtherObject.some_other_method(opts) end 

更新: Ruby 2.2引入了Binding#local_variables ,可用于代替Method#parameters 。 要小心,因为在方法中定义任何其他局部变量之前必须调用local_variables

 # Using Method#parameters def foo(one: 1, two: 2, three: 3) params = method(__method__).parameters.map(&:last) opts = params.map { |p| [p, eval(p.to_s)] }.to_h end #=> {:one=>1, :two=>2, :three=>3} # Using Binding#local_variables (Ruby 2.2+) def bar(one: 1, two: 2, three: 3) binding.local_variables.params.map { |p| [p, binding.local_variable_get(p)] }.to_h end #=> {:one=>1, :two=>2, :three=>3} 

当然! 只需使用双splat( ** )运算符。

 def print_all(**keyword_arguments) puts keyword_arguments end def mixed_signature(some: 'option', **rest) puts some puts rest end print_all example: 'double splat (**)', arbitrary: 'keyword arguments' # {:example=>"double splat (**)", :arbitrary=>"keyword arguments"} mixed_signature another: 'option' # option # {:another=>"option"} 

它就像常规的splat( * )一样,用于收集参数。 您甚至可以将关键字参数转发给另一个方法。

 def forward_all(*arguments, **keyword_arguments, &block) SomeOtherObject.some_other_method *arguments, **keyword_arguments, &block end 

我对此有一些乐趣,所以谢谢你。 这就是我想出的:

 describe "Argument Extraction Experiment" do let(:experiment_class) do Class.new do def method_with_mixed_args(one, two = 2, three:, four: 4) extract_args(binding) end def method_with_named_args(one:, two: 2, three: 3) extract_named_args(binding) end def method_with_unnamed_args(one, two = 2, three = 3) extract_unnamed_args(binding) end private def extract_args(env, depth = 1) caller_param_names = method(caller_locations(depth).first.label).parameters caller_param_names.map do |(arg_type,arg_name)| { name: arg_name, value: eval(arg_name.to_s, env), type: arg_type } end end def extract_named_args(env) extract_args(env, 2).select {|arg| [:key, :keyreq].include?(arg[:type]) } end def extract_unnamed_args(env) extract_args(env, 2).select {|arg| [:opt, :req].include?(arg[:type]) } end end end describe "#method_with_mixed_args" do subject { experiment_class.new.method_with_mixed_args("uno", three: 3) } it "should return a list of the args with values and types" do expect(subject).to eq([ { name: :one, value: "uno", type: :req }, { name: :two, value: 2, type: :opt }, { name: :three, value: 3, type: :keyreq }, { name: :four, value: 4, type: :key } ]) end end describe "#method_with_named_args" do subject { experiment_class.new.method_with_named_args(one: "one", two: 4) } it "should return a list of the args with values and types" do expect(subject).to eq([ { name: :one, value: "one", type: :keyreq }, { name: :two, value: 4, type: :key }, { name: :three, value: 3, type: :key } ]) end end describe "#method_with_unnamed_args" do subject { experiment_class.new.method_with_unnamed_args(2, 4, 6) } it "should return a list of the args with values and types" do expect(subject).to eq([ { name: :one, value: 2, type: :req }, { name: :two, value: 4, type: :opt }, { name: :three, value: 6, type: :opt } ]) end end end 

我选择返回一个数组,但您可以轻松修改它以返回一个哈希(例如,在初始检测后不关心参数类型)。

下面的语法怎么样?

要使其工作,请将params视为方法中的保留关键字,并将此行放在方法的顶部。

 def method(:name => nil, :color => nil, shoe_size => nil) params = params(binding) # params now contains the hash you're looking for end class Object def params(parent_binding) params = parent_binding.local_variables.reject { |s| s.to_s.start_with?('_') || s == :params }.map(&:to_sym) return params.map { |p| [ p, parent_binding.local_variable_get(p) ] }.to_h end end