1
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) 

这是 pre-2.7 public_send//send中的错误__send__吗?你会建议什么来克服这种差异?

你可以在这里现场检查这个失败。

4

1 回答 1

5

在 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 中无效。

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

public_send您可以通过验证 Ruby 如何解释其方法的参数来详细了解此处发生的情况。在 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, **{}) 时,您将看到关键字参数再次“包装”在 Hash 中:

: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 中,您必须严格区分关键字参数和位置参数。您可以在 ruby​​-lang.org 上的新闻条目中找到有关这些更改的一些附加说明。

于 2020-02-25T16:18:55.723 回答