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

phi*_*kov 1 ruby keyword-argument ruby-2.6 ruby-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
Run Code Online (Sandbox Code Playgroud)

然而,在 Ruby 2.6 中:

ArgumentError: wrong number of arguments (given 1, expected 0) 
Run Code Online (Sandbox Code Playgroud)

这是 2.7 之前的错误public_send//send__send__?您建议如何克服这种差异?

您可以在此处实时检查此故障。

Hol*_*ust 6

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

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

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

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

def two(**args)
  #...
end
Run Code Online (Sandbox Code Playgroud)

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

arguments = {foo: :bar}

one(arguments)
one(**arguments)

two(arguments)
two(**arguments)
Run Code Online (Sandbox Code Playgroud)

然而,在 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
Run Code Online (Sandbox Code Playgroud)

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

:a
[{}]
Run Code Online (Sandbox Code Playgroud)

使用 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
Run Code Online (Sandbox Code Playgroud)

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

Ruby 2.7 仍然包含回退行为,因此期望 Ruby 2.6 行为的代码仍然可以工作(尽管有警告)。在 Ruby 3.0 中,您必须严格区分关键字参数和位置参数。您可以在 ruby​​-lang.org 上的新闻条目中找到这些更改的一些附加描述。