命名参数作为 Ruby 中的局部变量
Posted
技术标签:
【中文标题】命名参数作为 Ruby 中的局部变量【英文标题】:Named arguments as local variables in Ruby 【发布时间】:2011-01-02 17:29:13 【问题描述】:在为方法使用命名参数时,我发现自己经常在 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] 代码来启动我的命名参数方法?
谢谢, Matthew O'Riordan
【问题讨论】:
听起来你真正想要的是命名参数。这些存在于 Ruby 2.0 中。 【参考方案1】:我不相信在 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)来允许访问“主要”对象,但性能可能不会很好。
总而言之,我认为使用困扰您的不太干燥的初始解决方案可能是最直接/最易读的。
【讨论】:
感谢您的意见。在尝试找到解决方案几天之后,我认为您的建议是保持它现在的样子可能是最好的!尝试将变量添加到 local_variables 似乎非常困难,而且 Ruby 也不鼓励这样做。我确实看到曾经有一个扩展 Binding.caller(在 extensions.rubyforge.org/rdoc/index.html 中),它可以提供获取调用绑定和插入局部变量的功能,但已被弃用。【参考方案2】: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)
【讨论】:
这似乎是一个非常漂亮的解决方案,并且可能非常有用 美丽优雅的方法。效果很好。感谢分享!【参考方案3】:我在ruby-talk-google 上找到了关于此的讨论,这似乎是对解析器的优化。局部变量在运行时已经被计算出来,因此 local_variables 已经在方法的开头设置。
def meth
p local_variables
a = 0
p local_variables
end
meth
# =>
[:a]
[:a]
这样,Ruby 不需要在运行时判断 a
是方法还是局部变量,但可以安全地假定它是局部变量。
(为了比较:在 Python 中,locals()
在函数开头为空。)
【讨论】:
【参考方案4】:在我的博客中(请参阅用户信息中的链接),我只是试图巧妙地解决这个问题。我在那里进行了更详细的介绍,但我的解决方案的核心是以下辅助方法:
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,这意味着少了一个需求,而且你得到的东西是根据正在处理的变量来设置的。
【讨论】:
【参考方案5】:这并不能解决问题,但我倾向于这样做
orange, lemon, grapefruit = [:orange, :lemon, :grapefruit].
map|key| args.fetch(key)
因为复制和粘贴 orange lemon grapefruit
位非常容易。
如果你觉得冒号的工作量太大,你可以这样做
orange, lemon, grapefruit = %worange, lemon, grapefruit.
map|str| str.gsub(",", "").to_sym.map|key| args.fetch(key)
【讨论】:
【参考方案6】:我发现自己今天想知道如何自己做这件事。我不仅想干掉我的代码,还想进行参数验证。
我遇到了a blog post by Juris Galang,他在其中解释了几种处理方法。他发布了a gem that encapsulates his ideas,看起来很有趣。
【讨论】:
以上是关于命名参数作为 Ruby 中的局部变量的主要内容,如果未能解决你的问题,请参考以下文章
默认形参和关键字实参,收集参数,命名关键字参数,return自定义返回,全局变量和局部变量,函数名的使用