Ruby 小技巧(二) - 使用 fetch 获取 Hash 和 Array 中的值
环境
-
Ruby: 3.0.1
-
Ruby On Rails: 6.1.4
什么是 fetch
下面是Rubydoc关于 fetch 的描述
fetch 的作用是从 hash 中获取给定的 key 对应的值。如果没有找到该 key,那么有以下几种情况会发生:没有传入其他参数时,抛出
KeyError
的异常;如果指定了默认值参数(第二个参数),则返回指定的值;如果给定了块,则会运行块并返回块的返回值;同时有默认值参数和块时,块会覆盖掉默认值参数。
当 hash 中不存在 fetch 所指定的键并且没有指定默认值时,抛出 KeyError 的异常:
h = {a: 1, b: 2}
h.fetch(:c) # => raise KeyError
当 hash 中不存在 fetch 所指定的键时,使用指定的默认值:
h = {a: 1, b: 2}
h.fetch(:c){ 3 } # => 3
# or
h.fetch(:c, 3) # => 3
上面演示了两种方式指定默认值,推荐使用块的形式,因为块具有延后执行(惰性)的特性。如果是以通过参数指定默认值的形式,并且默认值是通过调用方法得到的话,即使键存在,方法也会被调用:
h = {a: 1}
def default_value
p "method called!"
end
h.fetch(:a, default_value) # 这里会调用方法
# "method called"
# => 1
h.fetch(:a){ default_value } # 这里不会调用方法
# => 1
如果同时以参数形式和块形式指定了默认值,块形式的默认值会覆盖掉参数形式所指定的
h = {a: 1}
h.fetch(:b, 2){ 3 }
# warning: block supersedes default value argument
# => 3
块还可以接收一个参数,来获取传入的 key :
h = {a:1}
h.fetch(:b) do |key|
raise ArgumentError, "key #{key} not found"
end
# => key b not found (ArgumentError)
对于数组的用法也是类似的,只是key
被替换成了index
,并且index
不存在时抛出的异常类型是IndexError
而不是KeyError
。
arr = []
arr.fetch(0) # => IndexError: index 0 outside of array bounds: 0...0
arr.fetch(0, 1) # => 1
arr.fetch(0){ 1 } # => 1
arr.fetch(0, 1) { 2 } # => 2 同时会有一个 warning 提示 block 覆盖了 default
一些应用场景
假设要获取配置中的 database 中的 host ,如果没有则使用默认值
config = {}
config.fetch(:database, {}).fetch(:host, "127.0.0.1")
仅供思维启发,实际上这样场景使用dig
会更加直观:
config.dig(:database, :host) || "127.0.0.1"
关于dig
的使用可以看 Rails 小技巧(一) - 使用 dig 获取嵌套 Hash 和 Array 的数据
总结
fetch 中使用 default 和块指定默认值的区别:
-
首先是优先级的问题,上面已经指出块的优先级是高于默认值参数的
-
不管是否有找到值,默认值参数都会先求值(如果默认值参数是方法或其他可以执行的代码),而代码块是惰性求值的,也就是说块只有在找不到 key 的情况下才会触发。
fetch 和 [] 取值方式的不同:
-
fetch 在 key 不存在时会抛出
KeyError
或者是IndexError
的异常,而[]
只会返回 nil 。这点可以用在强制要求 key 存在的情况 -
fetch
可以指定默认值,不管是通过默认值参数还是块
参考
RubyDoc
《优雅的Ruby》
写在最后
文章内容错误的地方,欢迎及时指出。
最後までご覧いただいてありがとうございます
评论