Динамічне постійне призначення


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

дає мені помилку:

SyntaxError: похибка динамічного постійного призначення

Чому це вважається динамічною постійною? Я просто присвоюю йому рядок.


34
Динамічний констант - це щось на кшталт сухої води? :)
fl00r

39
Це не говорить про те, що константа динамічна. Це говорить про те, що призначення динамічне.
sepp2k

Відповіді:


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 або масив, ви можете "обдурити" і використовувати #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
ОП ніколи не говорив, що хоче змінити значення константи, а просто хотів призначити значення. Частий випадок використання, що призводить до цієї помилки Ruby, - це коли ви будуєте значення методу з інших активів часу виконання (змінні, аргументи командного рядка, ENV), як правило, в конструкторі, наприклад def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. Це один із тих випадків, коли у Рубі немає простого способу.
Арно Меурет

2
@ArnaudMeuret У цьому випадку потрібно змінну екземпляра (наприклад @variable), а не константу. Інакше ви будете перепризначати DBкожного разу, коли інстанціюєте новий екземпляр цього класу.
Ajedi32

2
@ Ajedi32 Ця ситуація зазвичай виникає із зовнішніх обмежень, а не варіантів дизайну, таких як мій приклад із Sequel. Моя думка полягає в тому, що призначати значення константі дозволено Рубі в певних межах, а не в інших. Раніше розробник міркував розумно, коли виконувати завдання. На цьому Рубі змінився. Не для кожного добре.
Арно Меурет

2
@ArnaudMeuret Я визнаю, що раніше ніколи не використовував Sequel, тому я не можу це сказати зі 100% впевненістю, але просто оглянувши документацію на Sequel, я нічого не бачу, що говорить про те, що ВИ ХОТЕЛИ призначати результат Sequel.connectпостійній імені DB . Насправді в документації прямо написано, що це лише рекомендація. Це не здається мені зовнішнім обмеженням.
Ajedi32

@ Ajedi32 1) Я ніколи не писав, що (ім'я постійного або навіть того, що вам довелося його десь зберігати), це лише приклад 2) Обмеження в тому, що ваше програмне забезпечення може не мати необхідної інформації, поки ви зазвичай не перебуваєте в динамічному контексті .
Арно Меурет

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

Рубі не подобається, що ви призначаєте константу всередині методу, оскільки це загрожує повторним призначенням. Перед нами кілька відповідей ТА дають альтернативу призначити його поза методом - але в класі, який є кращим місцем для його призначення.


1
Weicome до SO John. Йо може розглянути можливість вдосконалення цієї відповіді, додавши зразок коду того, що ви описуєте.
Клепт

0

Велике спасибі Доріану та Фрогз за те, що вони нагадали мені про метод масиву (і хеш) #replace, який може "замінити вміст масиву чи хеша".

Думка про те, що значення CONSTANT можна змінити, але з дратівливим попередженням, є одним з небагатьох концептуальних помилок Рубі - вони повинні бути повністю незмінними, або взагалі скидати постійну ідею. З точки зору кодера, константа є декларативним та навмисним, сигналом для інших, що "це значення справді незмінне після оголошення / присвоєння".

Але іноді "очевидна заява" фактично виключає інші, майбутні корисні можливості. Наприклад...

Там є законними прецеденти , коли значення «постійного в» може дійсно потрібно змінити, наприклад, повторно завантаження ARGV з REPL типу швидкої петлі, а потім перезапустивши ARGV через більш (наступний) OptionParser.parse! дзвінки - вуаля! Дає "аргументам командного рядка" абсолютно нову динамічну утиліту.

Практична проблема полягає або в припущенному припущенні, що "ARGV повинен бути константою", або у власному методі ініціалізації optparse, який жорстко кодує призначення ARGV екземпляру var @default_argv для подальшої обробки - цей масив (ARGV) дійсно повинен бути параметром, заохочуючи повторний аналіз та повторне використання, де це доречно. Належна параметризація з відповідним за замовчуванням (скажімо, ARGV) дозволить уникнути необхідності колись змінювати "постійний" ARGV. Просто кілька ¢-варті думок ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.