在使用方法的命名参数时,我发现自己经常在Ruby中编写我认为不必要的代码。
以下面的代码为例:
def my_method(args)
orange = args[:orange]
lemon = args[:lemon]
grapefruit = args[:grapefruit]
# code that uses
# orange, lemon & grapefruit in this format which is way prettier & concise than
# args[:orange] args[:lemon] args[:grapefruit]
puts "my_method variables: #{orange}, #{lemon}, #{grapefruit}"
end
my_method :orange => "Orange", :grapefruit => "Grapefruit"
我真的不喜欢这段代码,我不得不接受args并将值传递给局部变量,这些变量与DRY原则相反,并且通常占用我的方法中的空间。如果我不使用局部变量并且只使用args [:symbol]语法引用所有变量,那么代码就会变得难以理解。
我已经尝试过解决这个问题,但是我不知道如何在方法范围内使用eval或使用任何其他技术来定义局部变量。以下是下面的许多尝试之一,这会导致错误
def my_method_with_eval(args)
method_binding = binding
%w{ orange lemon grapefruit}.each { |variable| eval "#{variable} = args[:#{variable}]", method_binding; }
# code that uses
# orange, lemon & grapefruit in this format which is way prettier & concise than
# args[:orange] args[:lemon] args[:grapefruit]
puts "my_method_with_eval variables: #{orange}, #{lemon}, #{grapefruit}"
end
my_method_with_eval :orange => "Orange", :grapefruit => "Grapefruit"
运行该代码时,我只需
NameError: undefined local variable or method ‘orange’ for main:Object method my_method_with_eval in named_args_to_local_vars at line at top level in named_args_to_local_vars at line 9
任何人都有任何想法如何以某种方式简化此操作,以便我不必使用var = args [:var]代码的负载启动我的命名参数方法?
答案 0 :(得分:6)
我不相信在Ruby中有任何方法可以做到这一点(如果有人想出一个,请告诉我,我会更新或删除这个答案以反映它!) - 如果一个局部变量没有'尚未定义,没有办法用绑定动态定义它。你可以想象在调用eval之前做orange, lemon, grapefruit = nil
这样的事情,但是你可能遇到其他问题 - 例如,如果args [:orange]是字符串“Orange”,你最终会评估orange = Orange
与您当前的实施。
虽然使用标准库中的OpenStruct类(“可以工作”,但我的意思是“这取决于你的风格,a.orange
是否比任何更好args[:orange]
“):
require 'ostruct'
def my_method_with_ostruct(args)
a = OpenStruct.new(args)
puts "my_method_with_ostruct variables: #{a.orange}, #{a.lemon}, #{a.grapefruit}"
end
如果您不需要轻松访问此方法接收器上的任何状态或方法,则可以使用instance_eval,如下所示。
def my_method_with_instance_eval(args)
OpenStruct.new(args).instance_eval do
puts "my_method_with_instance_eval variables: #{orange}, #{lemon}, #{grapefruit}"
end
end
您甚至可以something tricky with method_missing(有关详情,请参阅here)以允许访问“主要”对象,但效果可能不会很好。
总而言之,我认为可能最简单/可读的方法是使用较少干扰你的DRY初始解决方案。
答案 1 :(得分:6)
Greg和Sand的答案合并:
require 'ostruct'
def my_method(args = {})
with args do
puts a
puts b
end
end
def with(args = {}, &block)
OpenStruct.new(args).instance_eval(&block)
end
my_method(:a => 1, :b => 2)
答案 2 :(得分:3)
我在ruby-talk-google上找到了关于此的讨论,它似乎是解析器的优化。局部变量已在运行时计算出来,因此local_variables已在方法的开头设置。
def meth
p local_variables
a = 0
p local_variables
end
meth
# =>
[:a]
[:a]
这样Ruby就不需要在运行时决定a
是方法还是局部变量,或者可以安全地假设它是局部变量。
(为了比较:在Python locals()
中,在函数的开头会为空。)
答案 3 :(得分:1)
在我的博客上(请参阅用户信息中的链接),我只是试图解决这个问题。我在那里详细介绍,但我的解决方案的核心是以下帮助方法:
def collect_named_args(given, expected)
# collect any given arguments that were unexpected
bad = given.keys - expected.keys
# if we have any unexpected arguments, raise an exception.
# Example error string: "unknown arguments sonething, anyhting"
raise ArgumentError,
"unknown argument#{bad.count > 1 ? 's' : ''}: #{bad.join(', ')}",
caller unless bad.empty?
Struct.new(*expected.keys).new(
*expected.map { |arg, default_value|
given.has_key?(arg) ? given[arg] : default_value
}
)
end # def collect_named_args
的调用如下:
def foo(arguments = {})
a = collect_named_args(arguments,
something: 'nothing',
everything: 'almost',
nothing: false,
anything: 75)
# Do something with the arguments
puts a.anything
end # def foo
我仍在试图弄清楚是否有办法将我的结果变成local_variables - 但正如其他人所说,Ruby不想这样做。我想你可以使用“with”技巧。
module Kernel
def with(object, &block)
object.instance_eval &block
end
end
然后
with(a) do
# Do something with arguments (a)
put anything
end
但由于几个原因,这感觉不尽如人意。
我喜欢上面的解决方案,因为它使用的是Struct而不是OpenStruct,这意味着需要的更少,并且只要处理哪些变量,就会设置回来的内容。
答案 4 :(得分:0)
这不能解决问题,但我倾向于
orange, lemon, grapefruit = [:orange, :lemon, :grapefruit].
map{|key| args.fetch(key)}
因为复制并粘贴orange lemon grapefruit
位非常容易。
如果你发现冒号过多,你可以做
orange, lemon, grapefruit = %w{orange, lemon, grapefruit}.
map{|str| str.gsub(",", "").to_sym}.map{|key| args.fetch(key)}
答案 5 :(得分:0)
我发现自己想知道今天如何做到这一点。我不仅想要干掉我的代码,而且我也希望进行参数验证。
我遇到了a blog post by Juris Galang,他在那里解释了几种处理它的方法。他发表的a gem that encapsulates his ideas看起来很有趣。