【问题标题】:Different behavior of `public_send` in Ruby 2.6 / 2.7Ruby 2.6 / 2.7 中“public_send”的不同行为
【发布时间】:2020-02-25 15:29:34
【问题描述】:
class A
  def a
    1
  end
end

a = A.new
x = {}

a.a(**x) # => 1 in both Ruby 2.6 and 2.7

a.public_send(:a, **x) # => 1 in Ruby 2.7

然而,在 Ruby 2.6 中:

ArgumentError: wrong number of arguments (given 1, expected 0) 

这是 2.7 之前的 public_send/send/__send__ 中的错误吗? 您有什么建议来克服这种差异?

你可以现场查看这个失败的here

【问题讨论】:

  • 原来new也遇到了同样的问题。

标签: ruby keyword-argument ruby-2.6 ruby-2.7


【解决方案1】:

在 Ruby 2.6 及之前的版本中,**argument 语法主要(但不完全)是传递哈希的语法糖。这样做是为了保持将变量散列作为最后一个参数传递给有效方法的约定。

在 Ruby 2.7 中,关键字参数在语义上被更新,不再映射到哈希参数。在这里,关键字参数是从位置参数处理的。

在 Ruby 2.6 及之前版本中,以下两个方法定义(至少在许多方面)等效:

def one(args={})
  #...
end

def two(**args)
  #...
end

在这两种情况下,您都可以传递具有相同结果的逐字散列或散点散列:

arguments = {foo: :bar}

one(arguments)
one(**arguments)

two(arguments)
two(**arguments)

然而,在 Ruby 2.7 中,您应该按原样传递关键字参数(之前的行为仍然有效,但已被警告弃用)。因此,对two(arguments) 的调用将在 2.7 中导致弃用警告,并在 Ruby 3.0 中无效。

在内部,散列散列参数(将关键字参数传递给方法)因此在 Ruby 2.7 中会导致空的关键字参数列表,但在 2.6 中会导致带有空散列的位置参数。

您可以通过验证 Ruby 如何解释其public_send 方法的参数来详细了解此处发生的情况。在 Ruby 2.6 及更早版本中,该方法实际上具有以下接口:

def public_send26(method_name, *args, &block);
  p method_name
  p args

  # we then effectively call
  #    self.method_name(*args, &block)
  # internally from C code

  nil
end

当在 Ruby 2.6 中以 public_send26(:a, **{}) 调用此方法时,您将看到关键字参数再次“包装”在哈希中:

:a
[{}]

在 Ruby 2.7 中,您拥有以下有效接口:

def public_send27(method_name, *args, **kwargs, &block);
  p method_name
  p args
  p **kwargs

  # Here, we then effectively call
  #    self.method_name(*args, **kwargs, &block)
  # internally from C code

  nil
end

您可以看到,关键字参数在 Ruby 2.7 中作为关键字参数单独处理和保留,而不是像在 Ruby 2.6 及更早版本中那样作为方法的常规位置哈希参数处理。

Ruby 2.7 仍然包含回退行为,因此预期 Ruby 2.6 行为的代码仍然有效(尽管带有警告)。在 Ruby 3.0 中,您必须严格区分关键字参数和位置参数。您可以在a news entry on ruby-lang.org 中找到有关这些更改的更多说明。

【讨论】:

  • 我明白你的意思,但有几件事让我感到困惑。 1. 如果args[],则带有可变参数one(*args) 的调用等效于one(),但同样适用于kwargs 的情况并非如此。 2. 如您所见,常规调用 (a.a(**x)) 按预期工作,而public_send 的行为则不同。我对位置和 kwargs 的分离或 2.7 的一般行为没有任何抱怨,我认为这是有问题的 2.7 之前的 public_send/send 的行为。
  • 同样的问题也适用于此。在 Ruby 2.6(及之前)中,飞溅的 Hash 参数(表示您的关键字参数)在对 public_send 的调用中转换为实际的 Hash,然后作为实际的 Hash 对象发送到您的 a 方法,因为该方法没有t 定义关键字参数(关键字参数在这里只是语法糖,如我的回答中所述)。在 Ruby 2.7 及更高版本中,传递的参数被保留为单独的关键字参数,因为您使用双飞溅传递它们。
猜你喜欢
  • 2018-04-21
  • 2015-01-18
  • 2017-02-15
  • 2014-05-26
  • 1970-01-01
  • 2013-11-28
  • 2013-01-22
  • 2015-02-25
  • 1970-01-01
相关资源
最近更新 更多