【问题标题】:Dynamic constant assignment动态常量赋值
【发布时间】:2011-10-06 10:32:08
【问题描述】:
class MyClass
  def mymethod
    MYCONSTANT = "blah"
  end
end

给我错误:

SyntaxError: 动态常量赋值错误

为什么这被认为是动态常数?我只是给它分配一个字符串。

【问题讨论】:

  • 动态常数类似于干水? :)
  • 并不是说常量是动态的。它表示分配是动态的。

标签: ruby


【解决方案1】:

ruby 中的常量不能在方法内部定义。 See the notes at the bottom of this page, for example

【讨论】:

    【解决方案2】:

    您的问题是每次运行该方法时,您都会为常量分配一个新值。这是不允许的,因为它使常量变为非常量;即使字符串的 contents 相同(目前,无论如何),每次调用该方法时,实际的字符串 object 本身都是不同的。例如:

    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"
    

    如果您真的想更改方法中的常量值,而您的常量是字符串或数组,您可以“作弊”并使用#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"
    

    【讨论】:

    • OP 从未说过他想更改常量的值,而只是想分配一个值。导致此 Ruby 错误的常见用例是当您从其他运行时资产(变量、命令行参数、ENV)构建方法中的值时,通常在构造函数中,例如def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end。这是 Ruby 没有简单方法的情况之一。
    • @ArnaudMeuret 对于这种情况,您需要一个实例变量(例如@variable),而不是常量。否则,每次实例化该类的新实例时,您都会重新分配 DB
    • @Ajedi32 这种情况通常是由外部约束而不是设计选择引起的,例如我的 Sequel 示例。我的观点是,Ruby 在某些范围内允许为常量分配值,而在其他范围内则不允许。过去由开发人员明智地选择何时执行任务。 Ruby 改变了这一点。不适合所有人。
    • @ArnaudMeuret 我承认我以前从未使用过 Sequel,所以我不能 100% 肯定地说这一点,但只是浏览一下 Sequel 的文档,我看不到任何内容表明你必须分配Sequel.connect 的结果到一个名为 DB 的常量。事实上,文档明确表示这只是一个建议。对我来说,这听起来不像是外部约束。
    • @Ajedi32 1)我从未写过(常量的名称,甚至您必须将其保存在某处)这只是一个示例 2)限制是您的软件可能没有必要的信息直到您通常处于动态环境中。
    【解决方案3】:

    在 Ruby 中,任何名称以大写字母开头的变量都是常量,您只能对其赋值一次。选择以下选项之一:

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

    【讨论】:

    • 谢天谢地,有人提到“任何名称以大写字母开头的变量都是常数!”
    【解决方案4】:

    因为 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
    

    【讨论】:

      【解决方案5】:

      你不能用大写字母命名变量,否则 Ruby 会假定它是一个常量,并希望它保持它的值不变,在这种情况下,改变它的值将是一个错误,即“动态常量赋值错误”。小写应该没问题

      class MyClass
        def mymethod
          myconstant = "blah"
        end
      end
      

      【讨论】:

        【解决方案6】:

        Ruby 不喜欢在方法内部分配常量,因为它有重新分配的风险。在我之前的几个 SO 答案给出了在方法之外分配它的替代方案 - 但在类中,这是分配它的更好地方。

        【讨论】:

        • Weicome to SO John。你可以考虑改进这个答案,添加一些你所描述的示例代码。
        【解决方案7】:

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

        CONSTANT 的值可以更改但带有恼人的警告的概念是 Ruby 为数不多的概念性错误步骤之一——这些错误步骤应该是完全不可变的,或者完全抛弃常量的想法。从编码人员的角度来看,常量是声明性的和有意的,向其他人发出信号,“这个值一旦声明/分配就真的不可更改。”

        但有时,“显而易见的声明”实际上排除了其他未来有用的机会。比如……

        合法用例可能确实需要更改“常量”值:例​​如,从类似 REPL 的提示循环中重新加载 ARGV,然后通过更多 (随后)OptionParser.parse!呼叫——瞧!为“命令行参数”提供全新的动态实用程序。

        实际问题是 要么 假设“ARGV 必须是一个常数”,要么 在 optparse 自己的初始化方法中,该方法对 ARGV 的分配进行硬编码到实例 var @default_argv 进行后续处理——该数组 (ARGV) 确实应该是一个参数,鼓励在适当的情况下重新解析和重用。适当的参数化和适当的默认值(例如,ARGV)将避免更改“恒定”ARGV 的需要。只是一些 2 美分的想法......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-27
          • 1970-01-01
          相关资源
          最近更新 更多