详解 Ruby 2.0 新特性 Keyword Argument

Ruby 2.0.0推出了一个新的特性Keyword Argument,中文名可能叫关键字参数或者命名参数,我在这里详细介绍下这个特性的具体细节。

什么是Keyword Argument呢?

在这里通过一个例子说明:

1
2
3
4
def log(msg, level: "ERROR", time: Time.now)
  puts "#{ time.ctime } [#{ level }] #{ msg }"
end
log("Hello!", level: "INFO")  #=> Mon Feb 18 01:46:22 2013 [INFO] Hello!

看起来很普通好像没有什么特别新的,是不是?

详细解释

在Ruby 1.9里面也有下面的调用方法方式:

1
log("Hello!", level: "INFO")

上面传的第二个参数是hash,定义像下面这样:

1
2
3
4
5
def log(msg, opt = {})
  level = opt[:level] || "ERROR"
  time  = opt[:time]  || Time.now
  puts "#{ time.ctime } [#{ level }] #{ msg }"
end

但是也许需求不是那么简单:

  • 希望hash如果传给我们不期望的key,需要抛出异常
  • 希望hash的其中一个key可以传nil

那我们需要把方法改成如下:

1
2
3
4
5
6
7
def log(*msgs)
  opt = msgs.last.is_a?(Hash) ? msgs.pop : {}
  level = opt.key?(:level) ? opt.delete(:level) : "ERROR"
  time  = opt.key?(:time ) ? opt.delete(:time ) : Time.now
  raise "unknown keyword: #{ opt.keys.first }" if !opt.empty?
  msgs.each {|msg| puts "#{ time.ctime } [#{ level }] #{ msg }" }
end

如果你处理类似需求的方法,会不会觉得很麻烦。如果使用Ruby 2.0 的新功能Keyword arguments,代码将会变得下面这么简单干净:

1
2
3
def log(msg, level: "ERROR", time: Time.now)
  puts "#{ time.ctime } [#{ level }] #{ msg }"
end

更多细节

我们可以调用的时候,参数部分省略

1
2
log("Hello!")                                  #=> Mon Feb 18 01:46:22 2013 [ERROR] Hello!
log("Hello!", level: "ERROR", time: Time.now)  #=> Mon Feb 18 01:46:22 2013 [ERROR] Hello!

Keyword argument的顺序不是很重要,但其他参数的顺序是不可以随便写的。

1
2
log("Hello!", time: Time.now, level: "ERROR")  #=> Mon Feb 18 01:46:22 2013 [ERROR] Hello!
log(level: "ERROR", time: Time.now, "Hello!")  # 运行错误

但如果你传入一个非期待的key时,将会报错

1
log("Hello!", date: Time.new)  #=> unknown keyword: date

如果你不想这个非期待的key报错,需要加入一个**开头的参数来保存这个key,

1
2
3
4
5
def log(msg, level: "ERROR", time: Time.now, **kwrest)
  puts "#{ time.ctime } [#{ level }] #{ msg }"
end

log("Hello!", date: Time.now)  #=> Mon Feb 18 01:46:22 2013 [ERROR] Hello!

当然你可以将Keyword argument和可变参数混合放一起使用,不太建议这么使用,会减少代码的可读性,增加了复杂度,容易出现bug。

1
2
3
def f(a, b, c, m = 1, n = 1, *rest, x, y, z, k: 1, **kwrest, &blk)
...
end

限制

可变参数和Keyword arguments混用的时候,容易出问题。例子:

1
2
3
4
5
6
7
def foo(*args, k: 1)
  p args
end

args = [{}, {}, {}]

foo(*args) #=> [{}, {}]

可变参数的最后一个值被赋给keyword arguments了

其次你也不能把**作为参数

1
2
3
def foo(**)
end
foo(k: 1) #=> unknown keyword: k

还有参数的key不能ruby里面的保留关键字,例:

1
2
3
def foo(if: false)
end
foo(if: true)

如果想使用if作为key,只能使用**参数如下:

1
2
3
4
def foo(**kwrest)
  p kwrest[:if]
end
foo(if: true) #=> true

评论