有没有办法在Ruby中访问方法参数?


102

Ruby和ROR的新手并且每天都喜欢它,所以这是我的问题,因为我不知道如何搜索它(并且我已经尝试过:))

我们有方法

def foo(first_name, last_name, age, sex, is_plumber)
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{SOMETHING}"    
end

因此,我正在寻找一种方法来将所有参数传递给方法,而不列出每个参数。因为这是Ruby,所以我认为有一种方法:)如果是Java,我只列出它们:)

输出为:

Method has failed, here are all method arguments {"Mario", "Super", 40, true, true}

1
Reha kralj svegami!
蚂蚁

1
我认为所有答案都应该指出,如果在运行参数发现方法之前“某些代码”更改了参数的值,它将显示新值,而不是传入的值。因此,您应该正确地抓住它们可以确定。就是说,我最喜欢的单行代码(感谢之前的回答)是:method(__method__).parameters.map { |_, v| [v, binding.local_variable_get(v)] }
Brian Deterling

Answers:


159

在Ruby 1.9.2及更高版本中,您可以在parameters方法上使用方法来获取该方法的参数列表。这将返回一个成对的列表,指示参数名称以及是否需要。

例如

如果你这样做

def foo(x, y)
end

然后

method(:foo).parameters # => [[:req, :x], [:req, :y]]

您可以使用特殊变量__method__来获取当前方法的名称。因此,在一种方法中,其参数名称可以通过

args = method(__method__).parameters.map { |arg| arg[1].to_s }

然后,您可以使用以下命令显示每个参数的名称和值:

logger.error "Method failed with " + args.map { |arg| "#{arg} = #{eval arg}" }.join(', ')

注意:由于此答案是最初编写的,因此在当前版本的Ruby eval中,不能再使用符号来调用。为了解决这个问题,to_s在建立参数名称列表时添加了一个显式的,即parameters.map { |arg| arg[1].to_s }


4
我需要一些时间来解密:)
Haris Krajina'2

3
让我知道哪些位需要解密,我将添加一些解释:)
mikej 2012年

3
+1这真的很棒又优雅;绝对是最佳答案。
安德鲁·马歇尔

5
我尝试使用Ruby 1.9.3,并且必须执行#{eval arg.to_s}才能正常工作,否则会遇到TypeError:无法将Symbol转换为String
Javid Jamae 2012年

5
同时,变得更好,我的技能也现在就了解此代码。
哈里斯·克拉吉纳

55

从Ruby 2.1开始,您可以使用binding.local_variable_get读取任何局部变量的值,包括方法参数(参数)。因此,您可以改善可接受的答案以避免邪恶 评估。

def foo(x, y)
  method(__method__).parameters.map do |_, name|
    binding.local_variable_get(name)
  end
end

foo(1, 2)  # => 1, 2

是最早的2.1吗?
uchuugaka 2015年

@uchuugaka是的,此方法在<2.1中不可用。
2015年

谢谢。这样做很不错:logger.info method __ +“#{args.inspect}” method(_method).parameters.map do | ,名称| logger.info“#{name} =” + binding.local_variable_get(name)结尾
Martin Cleaver

这是要走的路。
Ardee Aram

1
也可能有用-将参数转换为命名哈希:Hash[method(__method__).parameters.map.collect { |_, name| [name, binding.local_variable_get(name)] }]
sheba

19

一种解决方法是:

def foo(*args)
    first_name, last_name, age, sex, is_plumber = *args
    # some code
    # error happens here
    logger.error "Method has failed, here are all method arguments #{args.inspect}"    
end

2
工作,除非有更好的答案,否则将被投票接受。我唯一的问题是我不想失去方法签名,有些人会有Inteli的感觉,我不想失去它。
Haris Krajina

7

这是个有趣的问题。也许使用local_variables?但是除了使用eval之外,还必须有其他方法。我正在寻找内核文档

class Test
  def method(first, last)
    local_variables.each do |var|
      puts eval var.to_s
    end
  end
end

Test.new().method("aaa", 1) # outputs "aaa", 1

这还不错,为什么这个邪恶的解决方案呢?
Haris Krajina

在这种情况下还不错-使用eval()有时会导致安全漏洞。我认为可能有更好的方法:)但我承认Google在这种情况下不是我们的朋友
Raffaele 2012年

我要去解决这个问题,缺点是您无法创建可以处理此问题的帮助程序(模块),因为一旦离开原始方法,它就无法执行本地变量的评估。谢谢大家的信息。
Haris Krajina

这给我“ TypeError:无法将Symbol转换为String”,除非将其更改为eval var.to_s。另外,需要注意的是,如果您运行此循环之前定义了任何局部变量,则除了方法参数外还将包括它们。
安德鲁·马歇尔

6
这不是最优雅,最安全的方法-如果在方法内部定义局部变量然后调用local_variables,它将返回方法参数+所有局部变量。这可能会在您编写代码时导致错误。
Aliaksei Kliuchnikau 2012年

5

这可能会有所帮助...

  def foo(x, y)
    args(binding)
  end

  def args(callers_binding)
    callers_name = caller[0][/`.*'/][1..-2]
    parameters = method(callers_name).parameters
    parameters.map { |_, arg_name|
      callers_binding.local_variable_get(arg_name)
    }    
  end

1
除了这个稍微有点怪异的callers_name实现之外,您还可以将传递__method__binding
汤姆·罗德,

3

您可以定义一个常量,例如:

ARGS_TO_HASH = "method(__method__).parameters.map { |arg| arg[1].to_s }.map { |arg| { arg.to_sym => eval(arg) } }.reduce Hash.new, :merge"

并在您的代码中使用它,例如:

args = eval(ARGS_TO_HASH)
another_method_that_takes_the_same_arguments(**args)

2

在我进一步介绍之前,您要将太多的参数传递给foo。看起来所有这些参数都是模型的属性,对吗?您确实应该传递对象本身。演讲结束。

您可以使用“ splat”参数。它会将所有内容推到一个数组中。它看起来像:

def foo(*bar)
  ...
  log.error "Error with arguments #{bar.joins(', ')}"
end

在这一点上,方法签名对于代码的可读性和可重用性很重要。对象本身很好,但是您必须在某个地方创建实例,因此在调用该方法之前或在该方法中。我认为方法更好。例如创建用户方法。
Haris Krajina

用比我更聪明的人鲍勃·马丁(Bob Martin)在他的《干净的代码》一书中引用的话:“函数的理想参数个数为零(尼拉度)。接下来是一个(二元),紧接着是两个(二元)。三个参数(三合一)应该尽可能避免。三个以上(多合二)需要非常特殊的理由-然后无论如何都不要使用。” 他继续说我所说的话,许多相关的参数应该包装在一个类中并作为对象传递。这是一本好书,我强烈推荐。
汤姆L

对此,请不要提出任何疑问,但是请考虑一下:如果发现需要更多/更少/不同的参数,则您将破坏API,并且必须更新对该方法的每次调用。另一方面,如果您传递一个对象,则应用程序的其他部分(或服务的使用者)可以轻松自如。
汤姆L

我确实同意您的观点,例如在Java中,我将始终执行您的方法。但是我认为与ROR有所不同,这是为什么:
Haris Krajina 2012年

我确实同意您的观点,例如在Java中,我将始终执行您的方法。但是我认为ROR有所不同,这就是原因:如果要将ActiveRecord保存到数据库,并且具有保存它的方法,则必须在将哈希传递给save方法之前对其进行汇编。对于用户示例,我们设置名字,姓氏,用户名等,然后将哈希传递给save方法,该方法将执行某些操作并将其保存。这是一个问题,每个开发人员如何知道要放入哈希中的内容?它是活动记录,因此您必须去看数据库模式而不是汇编哈希,并且要非常小心,不要错过任何符号。
哈里斯·克拉吉纳

2

如果要更改方法签名,则可以执行以下操作:

def foo(*args)
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{args}"    
end

要么:

def foo(opts={})
  # some code
  # error happens here
  logger.error "Method has failed, here are all method arguments #{opts.values}"    
end

在这种情况下,插值argsopts.values将是一个数组,但是join如果使用逗号,则可以。干杯



1

如果您需要将参数用作哈希,并且不想用棘手的参数提取来污染方法的主体,请使用以下命令:

def mymethod(firstarg, kw_arg1:, kw_arg2: :default)
  args = MethodArguments.(binding) # All arguments are in `args` hash now
  ...
end

只需将此类添加到您的项目中:

class MethodArguments
  def self.call(ext_binding)
    raise ArgumentError, "Binding expected, #{ext_binding.class.name} given" unless ext_binding.is_a?(Binding)
    method_name = ext_binding.eval("__method__")
    ext_binding.receiver.method(method_name).parameters.map do |_, name|
      [name, ext_binding.local_variable_get(name)]
    end.to_h
  end
end

0

如果函数在某个类中,则可以执行以下操作:

class Car
  def drive(speed)
  end
end

car = Car.new
method = car.method(:drive)

p method.parameters #=> [[:req, :speed]] 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.