Ruby 2.6 / 2.7 中 `public_send` 的不同行为

Different behavior of `public_send` in Ruby 2.6 / 2.7

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__ 中的错误吗? 您有什么建议来克服这种差异?

你可以查看这个失败的 live here

在 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 的位置参数。

您可以通过验证 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, **{}),您将看到关键字参数再次 "wrapped" 在哈希中:

: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 中作为关键字参数单独处理和保留,而不是像在 [=43= 中那样作为方法的常规位置哈希参数处理。 ] 2.6 及更早版本。

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

中找到这些更改的一些额外说明