动态常数分配


139
class MyClass
  def mymethod
    MYCONSTANT = "blah"
  end
end

给我错误:

SyntaxError:动态常量分配错误

为什么将其视为动态常数?我只是给它分配一个字符串。


34
动态常数就像干水吗?:)
fl00r 2011年

39
它并不表示常量是动态的。它说分配是动态的。
sepp2k 2011年

Answers:


141

您的问题是,每次运行该方法时,您都将为常量分配一个新值。这是不允许的,因为它会使常数变得非常数。即使字符串的内容是相同的(暂时而言),每次调用该方法时,实际的字符串对象本身也有所不同。例如:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

也许,如果您解释了用例(为什么要在方法中更改常量的值),我们可以帮助您实现更好的实现。

也许您宁愿在类上有一个实例变量?

class MyClass
  class << self
    attr_accessor :my_constant
  end
  def my_method
    self.class.my_constant = "blah"
  end
end

p MyClass.my_constant #=> nil
MyClass.new.my_method

p MyClass.my_constant #=> "blah"

如果您确实想在方法中更改常量的值,并且您的常量是String或Array,则可以“作弊”并使用该#replace方法使对象采用新值,而无需实际更改对象:

class MyClass
  BAR = "blah"

  def cheat(new_bar)
    BAR.replace new_bar
  end
end

p MyClass::BAR           #=> "blah"
MyClass.new.cheat "whee"
p MyClass::BAR           #=> "whee"

19
OP从未说过他想更改常量的值,而只是想分配一个值。导致此Ruby错误的常见用例是,当您使用其他运行时资产(变量,命令行参数,ENV)在方法中构建值时,通常是在构造函数中,例如def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end。这是Ruby没有简单方法的情况之一。
Arnaud Meuret 2013年

2
@ArnaudMeuret在这种情况下,您需要实例变量(例如@variable),而不是常量。否则,DB每次实例化该类的新实例时,您都将重新分配。
Ajedi32

2
@ Ajedi32这种情况通常是由于外部约束而不是设计选择(例如我的Sequel示例)引起的。我的观点是,Ruby在某些范围内(而不是在其他范围内)允许为常量分配值。过去,开发人员需要明智地选择何时执行作业。Ruby对此进行了更改。不利于所有人。
Arnaud Meuret

2
@ArnaudMeuret我承认我以前从未使用过Sequel,所以我不能百分百地确定这一点,但是只要浏览一下Sequel的文档,我什么也看不到,说您必须将结果分配给Sequel.connect名为DB的常量。实际上,文档明确指出,这只是一个建议。这听起来不像是我的外部约束。
Ajedi32

@ Ajedi32 1)我从未写过(常量的名称,甚至您必须将其保留在某个地方)只是一个示例2)约束是您的软件可能没有必要的信息,除非您通常处于动态环境中。
2014年

69

因为Ruby中的常量不是要更改的,所以Ruby不鼓励您在可能多次执行的代码部分(例如内部方法)中分配给它们。

通常情况下,您应该在类本身内定义常量:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

如果由于某种原因确实需要在方法内部定义常量(也许用于某种元编程),则可以使用const_set

class MyClass
  def my_method
    self.class.const_set(:MY_CONSTANT, "foo")
  end
end

MyClass::MY_CONSTANT
#=> NameError: uninitialized constant MyClass::MY_CONSTANT

MyClass.new.my_method
MyClass::MY_CONSTANT #=> "foo"

同样,const_set在正常情况下,您并不是真正必须求助的东西。如果不确定是否真的要以这种方式分配给常量,则可以考虑以下替代方法之一:

类变量

类变量在许多方面表现得像常量。它们是类的属性,可以在定义它们的类的子类中访问。

区别在于类变量是可修改的,因此可以毫无问题地分配给内部方法。

class MyClass
  def self.my_class_variable
    @@my_class_variable
  end
  def my_method
    @@my_class_variable = "foo"
  end
end
class SubClass < MyClass
end

MyClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass
SubClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass

MyClass.new.my_method
MyClass.my_class_variable #=> "foo"
SubClass.my_class_variable #=> "foo"

类属性

类属性是一种“类上的实例变量”。它们的行为有点像类变量,只是它们的值不与子类共享。

class MyClass
  class << self
    attr_accessor :my_class_attribute
  end
  def my_method
    self.class.my_class_attribute = "blah"
  end
end
class SubClass < MyClass
end

MyClass.my_class_attribute #=> nil
SubClass.my_class_attribute #=> nil

MyClass.new.my_method
MyClass.my_class_attribute #=> "blah"
SubClass.my_class_attribute #=> nil

SubClass.new.my_method
SubClass.my_class_attribute #=> "blah"

实例变量

只是为了完整性,我应该提到:如果您需要分配一个只能在实例化类之后才能确定的值,那么您很有可能实际上正在寻找一个普通的旧实例变量。

class MyClass
  attr_accessor :instance_variable
  def my_method
    @instance_variable = "blah"
  end
end

my_object = MyClass.new
my_object.instance_variable #=> nil
my_object.my_method
my_object.instance_variable #=> "blah"

MyClass.new.instance_variable #=> nil

33

在Ruby中,任何名称以大写字母开头的变量都是常量,您只能分配一次。选择以下替代方法之一:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end

2
谢天谢地,有人提到“任何以大写字母开头的变量都是常数!”
ubienewbie


0

您不能使用大写字母来命名变量,否则Ruby会假设其为常量并希望使其值保持不变,在这种情况下,更改其值将是一个错误,即“动态常量赋值错误”。用小写应该可以

class MyClass
  def mymethod
    myconstant = "blah"
  end
end

0

Ruby不喜欢您在方法内部分配常量,因为它可能会导致重新分配。在我面前有几个SO答案提供了在方法之外分配它的替代方法-但在类中,这是分配它的更好位置。


1
欢迎来到约翰。您可能会考虑添加一些您所描述的示例代码来改善此答案。
Cleptus

0

非常感谢Dorian和Phrogz提醒我有关数组(和哈希)方法#replace的知识,该方法可以“替换数组或哈希的内容”。

常量值可以更改,但带有恼人的警告的观念,是Ruby少数概念上的失误之一-这些失误应该是完全不变的,或者是完全抛弃了不变的想法。从编码者的角度来看,常数是声明性的和有意的,这是对其他人的信号,即“一旦声明/分配,此值实际上是不可更改的”。

但是有时“明显的声明”实际上会排除其他未来有用的机会。例如...

某些合法的用例中,可能确实需要更改“常数”的值:例如,从类似于REPL的提示循环中重新加载ARGV,然后通过更多(后续)OptionParser.parse重新运行ARGV!电话-瞧!为“命令行参数”提供了一个全新的动态实用程序。

实际问题是,无论是与推定假设,即“ARGV必须是一个常量”,在optparse自己的初始化方法,其中硬编码ARGV的分配给实例VAR @default_argv进行后续处理-即阵列(ARGV)真应该作为一个参数,在适当的情况下鼓励重新解析和重用。适当的参数化以及适当的默认值(例如ARGV)将避免需要更改“恒定” ARGV。仅有价值2美分的想法...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.