Ruby“常量"似乎可以更改? [英] Ruby "CONSTANTS" seem to be INVISIBLY ALTERABLE?

查看:120
本文介绍了Ruby“常量"似乎可以更改?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我了解Ruby中的常量"按照惯例被称为常量,但实际上是可变的.但是,我给人的印象是,当它们被突变"时,会有警告:

I understand that "constants" in Ruby are by convention called constants but are in fact mutable. However I was under the impression that when they were "mutated" that there was a warning:

class Z2
 M = [0,1]
end

Z2::M # => [0, 1]
Z2::M = [0,3]
(irb):warning: already initialized constant Z2::M
(irb):warning: previous definition of M was here

但是我发现并非一直如此:

However I found this is not the case all the time:

a = Z2::M
a[1] = 2

Z2::M # => [0,2] and no warning

这是警告"系统中的空白吗?我推断常量的赋值将复制它,但是我想这也不对,因为常量和变量似乎指向同一个对象?这是否意味着需要冻结所有所谓的常量",以防止在未经警告的情况下对其进行更改?

Is this a gap in the "warning" system? I am inferring that assignment of a constant would duplicate it, but I guess that is not true either as it appears that constants and variables point to the same object? Does this mean that all so-called "constants" need to be frozen in order to prevent them from being changed without warning?

推荐答案

TL; DR

猴子补丁内核的简短#警告(请参阅 https://stackoverflow.com/a/662436/1301972 )引发异常,则您将无法阻止将其重新分配给常量本身.在惯用的Ruby代码中,这通常不是务实的问题,在该代码中,期望能够执行诸如重新打开类之类的操作,即使类名也是也是常量.

TL;DR

Short of monkey-patching Kernel#warn (see https://stackoverflow.com/a/662436/1301972) to raise an exception, you won't be able to prevent reassignment to the constant itself. This is generally not a pragmatic concern in idiomatic Ruby code where one expects to be able to do things like reopen classes, even though class names are also constants.

Ruby常量实际上并不是一成不变的,您不能

A Ruby constant isn't actually immutable, and you can't freeze a variable. However, you can get an exception to be raised when something attempts to modify the contents of a frozen object referenced by the constant.

冻结数组很容易:

CONSTANT_ONE = %w[one two three].freeze

,但是存储在此Array中的字符串实际上是对String对象的引用.因此,虽然您不能修改此Array,但仍可以(例如)修改索引0引用的String对象.要解决此问题,您不仅需要冻结Array,还需要冻结它所保存的对象.例如:

but the strings stored in this Array are really references to String objects. So, while you can't modify this Array, you can still (for example) modify the String object referenced by index 0. To solve this problem, you need to freeze not just the Array, but the objects it holds, too. For example:

CONSTANT = %w[one two three].map(&:freeze).freeze

CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String

CONSTANT << 'five'
# RuntimeError: can't modify frozen Array

用宝石递归冻结对象

由于冻结递归引用可能有点笨拙,因此很高兴知道有一个绝妙的选择.您可以使用ice_nine来冻结大多数对象:

Freezing Objects Recursively with a Gem

Since freezing recursive references can be a bit unwieldy, it's good to know there's a gem for that. You can use ice_nine to deep-freeze most objects:

require 'ice_nine'
require 'ice_nine/core_ext/object'

OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze

OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array

OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array

使用Ruby常数的更好方法

要考虑的另一种选择是调用 Object#dup 在将常量的值分配给另一个变量(例如类初始化程序中的实例变量)时,以确保您不会偶然更改常量的引用.例如:

A Better Way to Use Ruby Constants

Another option to consider is calling Object#dup when assigning the value of a constant to another variable, such as instance variables in your class initializers, in order to ensure you don't mutate your constant's references by accident. For example:

class Foo
  CONSTANT = 'foo'
  attr_accessor :variable

  def initialize
    @variable = CONSTANT.dup
  end
end

foo = Foo.new
foo.variable << 'bar'
#=> "foobar"

Foo::CONSTANT
#=> "foo"

这篇关于Ruby“常量"似乎可以更改?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆