何时在 Ruby 中使用关键字参数,也就是命名参数

Posted

技术标签:

【中文标题】何时在 Ruby 中使用关键字参数,也就是命名参数【英文标题】:When to use keyword arguments aka named parameters in Ruby 【发布时间】:2013-02-10 08:05:15 【问题描述】:

Ruby 2.0.0 支持关键字参数 (KA),我想知道这个特性在纯 Ruby 环境中的好处/用例是什么,尤其是考虑到由于关键字匹配需要每次调用带有关键字参数的方法时都会执行此操作。

require 'benchmark'

def foo(a:1,b:2,c:3)
  [a,b,c]
end

def bar(a,b,c)
  [a,b,c]
end

number = 1000000
Benchmark.bm(4) do |bm|
  bm.report("foo")  number.times  foo(a:7,b:8,c:9)   
  bm.report("bar")  number.times  bar(7,8,9)  
end

#           user     system      total        real
# foo    2.797000   0.032000   2.829000 (  2.906362)
# bar    0.234000   0.000000   0.234000 (  0.250010)

【问题讨论】:

【参考方案1】:

关键字参数有一些没有人提到过的明显优势。

首先,你没有耦合到参数的顺序。所以在你偶尔可能有一个 nil 参数的情况下,它看起来更干净:

def print_greeting(name, message = "Hello")
  puts "#message, #name"
end

print_greeting("John Connor", "Hasta la vista") 

如果你使用关键字参数:

def print_greeting(message: "Hello", name:)
  puts "#message, #name"
end

print_greeting(message: "Hasta la vista", name: "John Connor") 

甚至

print_greeting(name: "John Connor", message: "Goodbye")

它无需记住参数的顺序。但是,缺点是您必须记住参数的名称。这应该或多或少直观,并且可以说会导致更仔细地考虑方法签名。

使用关键字参数的另一个好处是当您有一个方法可能需要额外的参数时。

def create_person(name:, age:, height:)
  # make yourself some friends
end

如果您的系统要求现在需要了解某个人最喜欢的糖果棒,或者他们是否超重(因为食用了太多他们最喜欢的糖果棒),该怎么办?你怎么能使用关键字 args 来做到这一点? 简单:

def create_person(name:, age:, height:, favorite_candy:, overweight: true)
  # make yourself some fat friends
end

在关键字参数之前总是有散列,但这导致需要更多样板代码来提取和分配变量。样板代码 == 更多输入 == 更多潜在错别字 == 更少时间编写出色的 ruby​​ 代码。

def old_way(name, opts=)
  age    = opts[:age]
  height = opts[:height]
  # all the benefits as before, more arthritis and headaches  
end

如果您只是设置一个采用一个参数并且很可能永远不需要更改的方法:

def say_full_name(first_name, last_name)
  puts "#first_name #last_name"
end

那么应该避免使用关键字参数,因为这会对性能造成很小的影响。

【讨论】:

是否可以编写一个既能处理命名参数又能处理有序参数的初始化方法?【参考方案2】:

使用关键字参数的效率低下问题似乎不再是 ruby​​-2.2.0 的问题。

Feature #10440 修复了速度问题并在ruby-2.2.0 发布:

2014 年 11 月 3 日星期一 03:02:38 笹田光一

重写方法/块参数拟合逻辑以优化关键字参数/参数和 splat 参数。 Feature #10440(本票有详细说明)

您可以自己查看(使用与原始问题中给出的代码相同的代码):

(08:04:%) rvm use ruby-2.0.0-p247
Using /Users/adam/.rvm/gems/ruby-2.0.0-p247

(08:04:%) ruby keyword_benchmarks.rb

       user     system      total        real
foo    1.390000   0.060000   1.450000 (  1.451284)
bar    0.130000   0.000000   0.130000 (  0.122344)

(08:04:%)   rvm use ruby-2.2.0
Using /Users/adam/.rvm/gems/ruby-2.2.0

(08:04:%) ruby keyword_benchmarks.rb

       user     system      total        real
foo    0.140000   0.000000   0.140000 (  0.136194)
bar    0.110000   0.000000   0.110000 (  0.116246)

使用关键字 args 的性能损失仍然可以忽略不计,但我认为这是一个可以接受的权衡,以换取提高可读性和位置灵活性的好处。

【讨论】:

【参考方案3】:

由于 KA 是 ruby​​ 范围内的创新,我看到了两个主要优势:

将允许的参数限制为预定义的集合,就像 Rails 对 assert_valid_keys 所做的那样; 在代码块中使用该功能。

总结:

a = lambda  |name: "Leonardo", age: 67| [name, age] 
a.call # ⇒ ["Leonardo", 67]
a.call name: "Michelangelo", age: 88 # ⇒ ["Michelangelo", 88]
a.call name: "Schwarzenegger", alive: true # ⇒ ArgumentError: unknown keyword: alive

【讨论】:

【参考方案4】:

例如

一个函数

def welcome_message(message, options=)
  default_options = name: 'hoge'
  options = default_options.merge(options)

  "#message、#options[:name]"
end

可以写

def welcome_message(message, name: 'hoge')
  "#message、#name"
end

【讨论】:

要添加到答案中,请在需要将参数分配给方法定义中的变量时使用它们,并避免自己编写自定义代码。 请不要欺骗我们。 def welcome_message(message, options=:name => 'hoge') ; "#message、#options[:name]" ; endwelcome_message 的单行代码。但是,这种方法的主要缺点是需要将Hash 扩展为key_valid?,或者希望传递的密钥是正确的。 KA 方法让我们准确指定允许哪些键。 @mudasobwa 在我看来你错了;使用您的函数,welcome_message("message", :age => 22) 不会在结果中显示hoge,而是会使用keyword。您的函数为整个散列设置默认值,而不是为散列的已定义键设置默认值。 @oldergod 也许我的问题不是很清楚。我正在寻找使用此功能的令人信服的理由。您提供的更像是一次性案例,给我的好处是减少了 2 行代码,而不是更多。例如,在像 c# 这样的语言中,KA 的一个明显优势是只有一个方法定义,而不是编写数十个不同的方法签名来实现method overloading,而在 Ruby 中,方法重载不是一个有效的范例,所以这个好处是不相关的。另一个好处是Code Readability,它与 Ruby 相关。

以上是关于何时在 Ruby 中使用关键字参数,也就是命名参数的主要内容,如果未能解决你的问题,请参考以下文章

为啥不能在 Ruby 3 中结合 `...` 和命名参数?

命名参数作为 Ruby 中的局部变量

Ruby 中没有命名参数?

如何在子类中添加命名参数或在 Ruby 2.2 中更改它们的默认值?

命名关键字

命名这个 python/ruby 语言结构(使用数组值来满足函数参数)