Dynamische Konstantenzuweisung

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

gibt mir den Fehler:

SyntaxError: Zuweisungsfehler der dynamischen Konstante

Warum wird dies als dynamische Konstante angesehen? Ich weise ihm nur einen String zu.

der Spiegel
quelle
34
Dynamische Konstante ist so etwas wie trockenes Wasser? :)
fl00r
39
Es heißt nicht, dass die Konstante dynamisch ist. Es heißt, dass die Zuordnung dynamisch ist.
sepp2k

Antworten:

141

Ihr Problem ist, dass Sie der Konstante jedes Mal, wenn Sie die Methode ausführen, einen neuen Wert zuweisen. Dies ist nicht zulässig, da dadurch die Konstante nicht konstant wird. obwohl die Inhalte der Zeichenfolge gleich sind (für den Moment, jedenfalls), die tatsächliche String - Objekt selbst unterscheidet sich jedes Mal , wenn die Methode aufgerufen wird. Beispielsweise:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Wenn Sie Ihren Anwendungsfall erläutern - warum Sie den Wert einer Konstante in einer Methode ändern möchten -, können wir Ihnen möglicherweise bei einer besseren Implementierung helfen.

Vielleicht möchten Sie lieber eine Instanzvariable für die Klasse haben?

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"

Wenn Sie den Wert einer Konstante in einer Methode wirklich ändern möchten und Ihre Konstante ein String oder ein Array ist, können Sie die #replaceMethode "betrügen" und verwenden , um das Objekt dazu zu bringen, einen neuen Wert anzunehmen, ohne das Objekt tatsächlich zu ändern:

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"
Phrogz
quelle
19
Das OP hat nie gesagt, dass er den Wert der Konstante ändern möchte, sondern nur einen Wert zuweisen möchte. Der häufige Anwendungsfall, der zu diesem Ruby-Fehler führt, besteht darin, dass Sie den Wert in einer Methode aus anderen Laufzeit-Assets (Variablen, Befehlszeilenargumente, ENV) erstellen, normalerweise in einem Konstruktor, z def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. Es ist einer dieser Fälle, in denen Ruby keinen einfachen Weg hat.
Arnaud Meuret
2
@ArnaudMeuret In diesem Fall möchten Sie eine Instanzvariable (z. B. @variable), keine Konstante. Andernfalls würden Sie DBjedes Mal neu zuweisen, wenn Sie eine neue Instanz dieser Klasse instanziieren.
Ajedi32
2
@ Ajedi32 Diese Situation ergibt sich normalerweise aus externen Einschränkungen, nicht aus Entwurfsentscheidungen wie meinem Beispiel mit Sequel. Mein Punkt ist, dass Ruby in bestimmten Bereichen einen Wert einer Konstanten zuweisen darf und in anderen nicht. Früher war es Sache des Entwicklers, mit Bedacht zu entscheiden, wann die Aufgabe ausgeführt werden soll. Ruby hat das geändert. Nicht für alle gut.
Arnaud Meuret
2
@ArnaudMeuret Ich gebe zu, dass ich Sequel noch nie zuvor verwendet habe, daher kann ich dies nicht mit 100% iger Sicherheit sagen, aber wenn ich nur die Dokumentation für Sequel betrachte, sehe ich nichts, was besagt, dass Sie das Ergebnis Sequel.connecteiner Konstanten mit dem Namen DB zuweisen MÜSSEN . In der Dokumentation heißt es ausdrücklich, dass dies nur eine Empfehlung ist. Das klingt für mich nicht nach einer externen Einschränkung.
Ajedi32
@ Ajedi32 1) Ich habe nie geschrieben, dass (Name der Konstante oder sogar, dass Sie sie irgendwo aufbewahren mussten) dies nur ein Beispiel ist. 2) Die Einschränkung besteht darin, dass Ihre Software möglicherweise nicht über die erforderlichen Informationen verfügt, bis Sie sich normalerweise in einem dynamischen Kontext befinden .
Arnaud Meuret
69

Da Konstanten in Ruby nicht geändert werden sollen, rät Ruby Sie davon ab, sie in Teilen des Codes zuzuweisen, die möglicherweise mehrmals ausgeführt werden, z. B. innerhalb von Methoden.

Unter normalen Umständen sollten Sie die Konstante innerhalb der Klasse selbst definieren:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Wenn Sie aus irgendeinem Grund wirklich eine Konstante innerhalb einer Methode definieren müssen (möglicherweise für eine Art von Metaprogrammierung), können Sie Folgendes verwenden 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"

Auch hier const_setsollten Sie unter normalen Umständen nicht unbedingt darauf zurückgreifen müssen. Wenn Sie nicht sicher sind, ob Sie Konstanten auf diese Weise wirklich zuweisen möchten, sollten Sie eine der folgenden Alternativen in Betracht ziehen:

Klassenvariablen

Klassenvariablen verhalten sich in vielerlei Hinsicht wie Konstanten. Sie sind Eigenschaften einer Klasse und in Unterklassen der Klasse, für die sie definiert sind, zugänglich.

Der Unterschied besteht darin, dass Klassenvariablen modifizierbar sein sollen und daher problemlos internen Methoden zugewiesen werden können.

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"

Klassenattribute

Klassenattribute sind eine Art "Instanzvariable für eine Klasse". Sie verhalten sich ein bisschen wie Klassenvariablen, außer dass ihre Werte nicht mit Unterklassen geteilt werden.

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"

Instanzvariablen

Und der Vollständigkeit halber sollte ich wahrscheinlich erwähnen: Wenn Sie einen Wert zuweisen müssen, der erst bestimmt werden kann, nachdem Ihre Klasse instanziiert wurde, besteht eine gute Chance, dass Sie tatsächlich nach einer einfachen alten Instanzvariablen suchen.

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
Ajedi32
quelle
33

In Ruby ist jede Variable, deren Name mit einem Großbuchstaben beginnt, eine Konstante, die Sie nur einmal zuweisen können. Wählen Sie eine dieser Alternativen:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
quelle
2
Gott sei Dank erwähnte jemand, dass "jede Variable, deren Name mit einem Großbuchstaben beginnt, eine Konstante ist!"
Ubienewbie
0

Sie können eine Variable nicht mit Großbuchstaben benennen, oder Ruby nimmt an, dass sie eine Konstante ist, und möchte, dass sie ihren Wert konstant hält. In diesem Fall wäre das Ändern ihres Werts ein Fehler, ein "dynamischer Konstantenzuweisungsfehler". Mit Kleinbuchstaben sollte in Ordnung sein

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
Jose Paez
quelle
0

Ruby mag es nicht, dass Sie die Konstante innerhalb einer Methode zuweisen, da dies zu einer erneuten Zuweisung führen kann. Einige SO-Antworten vor mir bieten die Alternative, sie außerhalb einer Methode zuzuweisen - aber in der Klasse, die ein besserer Ort ist, um sie zuzuweisen.

John
quelle
1
Weicome zu SO John. Möglicherweise möchten Sie diese Antwort verbessern und einen Beispielcode für das hinzufügen, was Sie beschreiben.
Cleptus
0

Vielen Dank an Dorian und Phrogz, die mich an die Array- (und Hash-) Methode #replace erinnert haben, die "den Inhalt eines Arrays oder Hashs ersetzen kann".

Die Vorstellung, dass der Wert eines CONSTANT geändert werden kann, aber mit einer nervigen Warnung, ist einer der wenigen konzeptionellen Fehltritte von Ruby - diese sollten entweder vollständig unveränderlich sein oder die konstante Idee insgesamt verwerfen. Aus der Sicht eines Codierers ist eine Konstante deklarativ und beabsichtigt, ein Signal an andere, dass "dieser Wert nach der Deklaration / Zuweisung wirklich unveränderlich ist".

Aber manchmal schließt eine "offensichtliche Erklärung" tatsächlich andere, zukünftige nützliche Gelegenheiten aus. Beispielsweise...

Es gibt legitime Anwendungsfälle, in denen der Wert einer "Konstante" möglicherweise wirklich geändert werden muss: Zum Beispiel das erneute Laden von ARGV aus einer REPL-ähnlichen Eingabeaufforderungsschleife und das erneute Ausführen von ARGV über weitere (nachfolgende) OptionParser.parse! Anrufe - voila! Verleiht "Befehlszeilenargumenten" ein völlig neues dynamisches Dienstprogramm.

Das praktische Problem besteht entweder in der vermuteten Annahme, dass "ARGV eine Konstante sein muss", oder in der eigenen Initialisierungsmethode von optparse, die die Zuordnung von ARGV zur Instanz var @default_argv für die nachfolgende Verarbeitung fest codiert - dieses Array (ARGV) wirklich sollte ein Parameter sein, der gegebenenfalls eine erneute Analyse und Wiederverwendung fördert. Eine ordnungsgemäße Parametrisierung mit einem geeigneten Standard (z. B. ARGV) würde die Notwendigkeit vermeiden, das "konstante" ARGV jemals zu ändern. Nur ein paar Gedanken wert ...

Lorin Ricker
quelle