I have a method that looks like this:
我有一个看起来像这样的方法:
def method(:name => nil, :color => nil, shoe_size => nil)
SomeOtherObject.some_other_method(THE HASH THAT THOSE KEYWORD ARGUMENTS WOULD MAKE)
end
For any given call, I can accept any combination of optional values. I like the named arguments, because I can just look at the method's signature to see what options are available.
对于任何给定的调用,我可以接受任意值的任意组合。我喜欢命名参数,因为我可以查看方法的签名以查看可用的选项。
What I don't know is if there is a shortcut for what I have described in capital letters in the code sample above.
我不知道的是,上面的代码示例中是否有我用大写字母描述的快捷方式。
Back in the olden days, it used to be:
回到过去,它曾经是:
def method(opts)
SomeOtherObject.some_other_method(opts)
end
Elegant, simple, almost cheating.
优雅,简单,几乎作弊。
Is there a shortcut for those Keyword Arguments or do I have to reconstitute my options hash in the method call?
这些关键字参数是否有快捷方式,还是我必须在方法调用中重构我的选项哈希?
4 个解决方案
#1
16
Yes, this is possible, but it's not very elegant.
是的,这是可能的,但它不是很优雅。
You'll have to use the parameters
method, which returns an array of the method's parameters and their types (in this case we only have keyword arguments).
您将不得不使用参数方法,该方法返回方法参数及其类型的数组(在本例中,我们只有关键字参数)。
def foo(one: 1, two: 2, three: 3)
method(__method__).parameters
end
#=> [[:key, :one], [:key, :two], [:key, :three]]
Knowing that, there's various ways how to use that array to get a hash of all the parameters and their provided values.
知道了,有多种方法可以使用该数组来获取所有参数及其提供值的哈希值。
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}
So your example would look like
所以你的例子看起来像
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
Think carefully about using this. It's clever but at the cost of readability, others reading your code won't like it.
仔细考虑使用它。它很聪明但是以可读性为代价,其他人阅读你的代码却不喜欢它。
You can make it slightly more readable with a helper method.
您可以使用辅助方法使其更具可读性。
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
Update: Ruby 2.2 introduced Binding#local_variables
which can be used instead of Method#parameters
. Be careful because you have to call local_variables
before defining any additional local variables inside the method.
更新:Ruby 2.2引入了Binding#local_variables,可用于代替Method#参数。要小心,因为在方法中定义任何其他局部变量之前必须调用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}
#2
7
Of course! Just use the double splat (**
) operator.
当然!只需使用双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"}
It works just like the regular splat (*
), used for collecting parameters. You can even forward the keyword arguments to another method.
它就像常规的splat(*)一样,用于收集参数。您甚至可以将关键字参数转发给另一个方法。
def forward_all(*arguments, **keyword_arguments, &block)
SomeOtherObject.some_other_method *arguments,
**keyword_arguments,
&block
end
#3
1
I had some fun with this, so thanks for that. Here's what I came up with:
我对此有一些乐趣,所以谢谢你。这就是我想出的:
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
I chose to return an array, but you could easily modify this to return a hash instead (for instance, by not caring about the argument type after the initial detection).
我选择返回一个数组,但您可以轻松修改它以返回一个哈希(例如,在初始检测后不关心参数类型)。
#4
0
How about the syntax below?
下面的语法怎么样?
For it to work, treat params
as a reserved keyword in your method and place this line at the top of the method.
要使其工作,请将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
#1
16
Yes, this is possible, but it's not very elegant.
是的,这是可能的,但它不是很优雅。
You'll have to use the parameters
method, which returns an array of the method's parameters and their types (in this case we only have keyword arguments).
您将不得不使用参数方法,该方法返回方法参数及其类型的数组(在本例中,我们只有关键字参数)。
def foo(one: 1, two: 2, three: 3)
method(__method__).parameters
end
#=> [[:key, :one], [:key, :two], [:key, :three]]
Knowing that, there's various ways how to use that array to get a hash of all the parameters and their provided values.
知道了,有多种方法可以使用该数组来获取所有参数及其提供值的哈希值。
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}
So your example would look like
所以你的例子看起来像
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
Think carefully about using this. It's clever but at the cost of readability, others reading your code won't like it.
仔细考虑使用它。它很聪明但是以可读性为代价,其他人阅读你的代码却不喜欢它。
You can make it slightly more readable with a helper method.
您可以使用辅助方法使其更具可读性。
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
Update: Ruby 2.2 introduced Binding#local_variables
which can be used instead of Method#parameters
. Be careful because you have to call local_variables
before defining any additional local variables inside the method.
更新:Ruby 2.2引入了Binding#local_variables,可用于代替Method#参数。要小心,因为在方法中定义任何其他局部变量之前必须调用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}
#2
7
Of course! Just use the double splat (**
) operator.
当然!只需使用双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"}
It works just like the regular splat (*
), used for collecting parameters. You can even forward the keyword arguments to another method.
它就像常规的splat(*)一样,用于收集参数。您甚至可以将关键字参数转发给另一个方法。
def forward_all(*arguments, **keyword_arguments, &block)
SomeOtherObject.some_other_method *arguments,
**keyword_arguments,
&block
end
#3
1
I had some fun with this, so thanks for that. Here's what I came up with:
我对此有一些乐趣,所以谢谢你。这就是我想出的:
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
I chose to return an array, but you could easily modify this to return a hash instead (for instance, by not caring about the argument type after the initial detection).
我选择返回一个数组,但您可以轻松修改它以返回一个哈希(例如,在初始检测后不关心参数类型)。
#4
0
How about the syntax below?
下面的语法怎么样?
For it to work, treat params
as a reserved keyword in your method and place this line at the top of the method.
要使其工作,请将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