【问题标题】:Named arguments as local variables in Ruby命名参数作为 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 中。

标签: ruby variables arguments


【解决方案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 = %w{orange, 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,看起来很有趣。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-24
          • 1970-01-01
          • 2011-02-14
          • 2017-07-21
          • 2012-12-28
          • 1970-01-01
          • 2019-11-06
          • 1970-01-01
          相关资源
          最近更新 更多