我有一个看起来像这样的方法:
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
优雅,简单,几乎是作弊。
这些关键字参数是否有快捷方式,还是我必须在方法调用中重构我的选项哈希?
答案 0 :(得分:17)
是的,这个 是可能的,但它不是很优雅。
你必须使用the parameters
method,它返回方法参数及其类型的数组(在这种情况下,我们只有关键字参数)。
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}
。
{{1}}
答案 1 :(得分:8)
当然!只需使用双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
答案 2 :(得分:1)
我对此很开心,所以谢谢你。这就是我想出的:
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
我选择返回一个数组,但您可以轻松修改它以返回一个哈希(例如,在初始检测后不关心参数类型)。
答案 3 :(得分:0)
下面的语法怎么样?
要使其正常工作,请将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
答案 4 :(得分:0)
@Dennis的答案很有用且很有教育意义。但是,我注意到Binding#local_variables
将返回 all 所有局部变量,而不管何时执行local_variables
:
def locals_from_binding(binding_:)
binding_.local_variables.map { |var|
[var, binding_.local_variable_get(var)]
}.to_h
end
def m(a:, b:, c:)
args = locals_from_binding(binding_: binding)
pp args
d = 4
end
m(a: 1, b: 3, c: 5)
# Prints:
# {:a=>1, :b=>3, :c=>5, :args=>nil, :d=>nil}
# Note the presence of :d
我提出了一种混合解决方案:
def method_args_from_parameters(binding_:)
method(caller_locations[0].label)
.parameters.map(&:last)
.map { |var|
[var, binding_.local_variable_get(var)]
}.to_h
end
def m(a:, b:, c:)
args = method_args_from_parameters(binding_: binding)
pp args
d = 4
end
m(a: 1, b: 3, c: 5)
# Prints:
# {:a=>1, :b=>3, :c=>5}
# Note the absence of :d