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 和块指定默认值的区别:

  1. 首先是优先级的问题,上面已经指出块的优先级是高于默认值参数的

  2. 不管是否有找到值,默认值参数都会先求值(如果默认值参数是方法或其他可以执行的代码),而代码块是惰性求值的,也就是说块只有在找不到 key 的情况下才会触发。

fetch 和 [] 取值方式的不同:

  1. fetch 在 key 不存在时会抛出KeyError或者是IndexError的异常,而[]只会返回 nil 。这点可以用在强制要求 key 存在的情况

  2. fetch可以指定默认值,不管是通过默认值参数还是块

参考

RubyDoc
《优雅的Ruby》

写在最后

文章内容错误的地方,欢迎及时指出。

最後までご覧いただいてありがとうございます

Ruby 小技巧系列

评论