Ruby: 初始化一个散列:分配值,引发未定义的键访问,然后冻结,一次完成?
Ruby: Initializing a hash: Assign values, throw on undefined key access, and freeze, all at once?
在 Ruby 中,我想初始化一个新的散列,这样:
- 哈希被分配了一组特定的初始键值对;
- 散列配置为在尝试检索未定义键的值时引发错误;
- 哈希已冻结(无法进一步修改)。
是否有一种优雅的 Ruby 风格的方式一次性完成此设置?
我知道这可以分三行完成,例如:
COIN_SIDES = { heads: 'heads', tails: 'tails' }
COIN_SIDES.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" }
COIN_SIDES.freeze
我不确定这是否非常优雅,但在一行(长)行中实现此目的的一种方法是使用 .tap
:
COIN_SIDES = { heads: 'heads', tails: 'tails' }.tap { |cs| cs.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" } }.tap(&:freeze)
这种方法至少避免了 RuboCop: Freeze mutable objects assigned to constants [Style/MutableConstant]
warning generated when running the RuboCop 上面原始问题中代码的 3 行版本的 linter。
您可以通过使用 default_proc 初始化散列然后使用合并添加组件来完成此操作!:
h = Hash.new{|hash, key| raise KeyError, "Key '#{key}' not found"}.merge!({ heads: 'heads', tails: 'tails' }).freeze
您可以通过自定义 class 来完成大部分功能,唯一的缺点是它不是真正的哈希,因此您需要明确添加额外的功能,例如 .keys
, each
,如果需要的话:
class HashLike
def initialize(hsh)
singleton_class.attr_reader *hsh.keys
hsh.each { |k,v| instance_variable_set "@#{k}", v }
end
end
hashlike = HashLike.new(some_value: 1)
hashlike.some_value # 1
hashlike.missing_value # NoMethodError
hashlike.some_value = 2 # NoMethodError
另一种类似的方式:
class HashLike2
def initialize(hsh)
@hsh = hsh
end
def [](key)
@hsh.fetch(key)
end
end
hashlike2 = HashLike2.new(some_value: 1)
hashlike2[:some_value] # 1
hashlike2[:missing_value] # KeyError
hashlike2[:some_value] = 2 # NoMethodError
但在我看来,没有太多理由这样做。您可以轻松地将原来的 3 行移动到某个地方的方法中,然后无论是 3 行还是 1 行都没有关系。
在 Ruby 中,我想初始化一个新的散列,这样:
- 哈希被分配了一组特定的初始键值对;
- 散列配置为在尝试检索未定义键的值时引发错误;
- 哈希已冻结(无法进一步修改)。
是否有一种优雅的 Ruby 风格的方式一次性完成此设置?
我知道这可以分三行完成,例如:
COIN_SIDES = { heads: 'heads', tails: 'tails' }
COIN_SIDES.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" }
COIN_SIDES.freeze
我不确定这是否非常优雅,但在一行(长)行中实现此目的的一种方法是使用 .tap
:
COIN_SIDES = { heads: 'heads', tails: 'tails' }.tap { |cs| cs.default_proc = -> (h, k) { raise KeyError, "Key '#{k}' not found" } }.tap(&:freeze)
这种方法至少避免了 RuboCop: Freeze mutable objects assigned to constants [Style/MutableConstant]
warning generated when running the RuboCop 上面原始问题中代码的 3 行版本的 linter。
您可以通过使用 default_proc 初始化散列然后使用合并添加组件来完成此操作!:
h = Hash.new{|hash, key| raise KeyError, "Key '#{key}' not found"}.merge!({ heads: 'heads', tails: 'tails' }).freeze
您可以通过自定义 class 来完成大部分功能,唯一的缺点是它不是真正的哈希,因此您需要明确添加额外的功能,例如 .keys
, each
,如果需要的话:
class HashLike
def initialize(hsh)
singleton_class.attr_reader *hsh.keys
hsh.each { |k,v| instance_variable_set "@#{k}", v }
end
end
hashlike = HashLike.new(some_value: 1)
hashlike.some_value # 1
hashlike.missing_value # NoMethodError
hashlike.some_value = 2 # NoMethodError
另一种类似的方式:
class HashLike2
def initialize(hsh)
@hsh = hsh
end
def [](key)
@hsh.fetch(key)
end
end
hashlike2 = HashLike2.new(some_value: 1)
hashlike2[:some_value] # 1
hashlike2[:missing_value] # KeyError
hashlike2[:some_value] = 2 # NoMethodError
但在我看来,没有太多理由这样做。您可以轻松地将原来的 3 行移动到某个地方的方法中,然后无论是 3 行还是 1 行都没有关系。