การกำหนดค่าคงที่แบบไดนามิก


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"

หากคุณจริงๆต้องการที่จะเปลี่ยนค่าของคงที่ในวิธีการและคุณคงเป็นสตริงหรืออาร์เรย์คุณสามารถ 'โกง' และใช้#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 ไม่เคยบอกว่าเขาต้องการเปลี่ยนค่าของค่าคงที่ แต่เพียงต้องการที่จะกำหนดค่า กรณีที่ใช้บ่อยที่นำไปสู่ข้อผิดพลาดทับทิมนี้คือเมื่อคุณสร้างค่าในวิธีการจากสินทรัพย์อื่นเวลาทำงาน (ตัวแปรอาร์กิวเมนต์บรรทัดคำสั่ง, ENV) def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") endมักจะอยู่ในตัวสร้างเช่น เป็นหนึ่งในกรณีเหล่านั้นที่ Ruby ไม่มีวิธีการง่ายๆ
Arnaud Meuret

2
@ArnaudMeuret สำหรับกรณีนี้คุณต้องการตัวแปรอินสแตนซ์ (เช่น@variable) ไม่ใช่ค่าคงที่ มิฉะนั้นคุณจะกำหนดใหม่DBทุกครั้งที่คุณสร้างอินสแตนซ์ใหม่ของคลาสนั้น
Ajedi32

2
@ Ajedi32 สถานการณ์นี้มักเกิดจากข้อ จำกัด ภายนอกไม่ใช่ตัวเลือกการออกแบบเช่นตัวอย่างของฉันกับ Sequel ประเด็นของฉันคือการกำหนดค่าให้กับค่าคงที่ได้รับอนุญาตจาก Ruby ในขอบเขตที่แน่นอนและไม่ใช่ค่าอื่น มันเคยขึ้นอยู่กับนักพัฒนาที่จะเลือกอย่างชาญฉลาดเมื่อต้องทำการมอบหมาย Ruby มีการเปลี่ยนแปลงในสิ่งนี้ ไม่ใช่เพื่อประโยชน์ของทุกคน
Arnaud Meuret

2
@ArnaudMeuret ฉันจะยอมรับว่าฉันไม่เคยใช้ Sequel มาก่อนดังนั้นฉันจึงไม่สามารถพูดสิ่งนี้ได้อย่างแน่นอน 100% แต่เพียงแค่ดูเอกสารของ Sequel ฉันไม่เห็นอะไรที่บอกว่าคุณต้องกำหนดผลลัพธ์Sequel.connectให้กับ DB ที่คงที่ . ในความเป็นจริงเอกสารอธิบายอย่างชัดเจนว่าเป็นเพียงคำแนะนำ ไม่ได้ฟังเหมือนข้อ จำกัด ภายนอกสำหรับฉัน
Ajedi32

@ Ajedi32 1) ฉันไม่เคยเขียนว่า (ชื่อของค่าคงที่หรือแม้แต่ที่คุณต้องเก็บไว้ที่ไหนสักแห่ง) มันเป็นเพียงตัวอย่าง 2) ข้อ จำกัด ที่ซอฟต์แวร์ของคุณอาจไม่มีข้อมูลที่จำเป็นจนกว่าคุณจะอยู่ในบริบทแบบไดนามิก .
Arnaud Meuret

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 เป็นประเภท "ตัวแปรอินสแตนซ์ในคลาส" พวกเขาทำตัวเหมือนตัวแปรคลาสยกเว้นว่าค่าของพวกเขาจะไม่ถูกแชร์กับคลาสย่อย

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

ทับทิมไม่ชอบที่คุณกำลังกำหนดค่าคงที่ภายในของวิธีการเพราะมันเสี่ยงต่อการกำหนดใหม่ คำตอบ SO จำนวนมากก่อนที่ฉันจะให้ทางเลือกในการมอบหมายนอกวิธีการ - แต่ในชั้นเรียนซึ่งเป็นสถานที่ที่ดีกว่าในการมอบหมาย


1
Weicome ถึง SO John โย่อาจพิจารณาปรับปรุงคำตอบนี้โดยเพิ่มโค้ดตัวอย่างที่คุณกำลังอธิบาย
Cleptus

0

ขอบคุณ Dorian และ Phrogz สำหรับเตือนฉันเกี่ยวกับวิธีอาร์เรย์ (และแฮช) #replace ซึ่งสามารถ "แทนที่เนื้อหาของอาร์เรย์หรือแฮช"

ความคิดที่ว่าค่าคงที่ของค่าคงที่สามารถเปลี่ยนแปลงได้ แต่ด้วยการเตือนที่น่ารำคาญเป็นหนึ่งในขั้นตอนการคิดผิดพลาดที่ไม่กี่ขั้นตอนของ Ruby - สิ่งเหล่านี้ควรจะไม่เปลี่ยนแปลงอย่างสมบูรณ์หรือทิ้งความคิดคงที่ทั้งหมด จากมุมมองของโคเดอร์นค่าคงที่คือการประกาศและจงใจสัญญาณที่บ่งบอกว่า "ค่านี้ไม่สามารถเปลี่ยนแปลงได้จริงเมื่อประกาศ / กำหนดแล้ว"

แต่บางครั้ง "ประกาศชัดเจน" จริง ๆ แล้วรอโอกาสอื่น ๆ ที่เป็นประโยชน์ในอนาคต ตัวอย่างเช่น...

มีอยู่เช่นเรื่องการโหลด ARGV จาก REPL เหมือนพรอมต์วงแล้ว rerunning ARGV ผ่านมากขึ้น (ต่อมา) OptionParser.parse: กรณีการใช้งานถูกต้องตามกฎหมายที่ "คงที่" ค่าอาจจำเป็นจริงๆที่จะเปลี่ยนแปลง! โทร - voila! ให้ "บรรทัดคำสั่ง args" ยูทิลิตี้แบบไดนามิกใหม่ทั้งหมด

ปัญหาที่เกิดขึ้นจริงนั้นเป็นไปได้ด้วยสมมติฐานที่สันนิษฐานว่า "ARGV ต้องเป็นค่าคงที่" หรือในวิธีการเริ่มต้นของ optparse เองซึ่งรหัสยาก ๆ ที่กำหนด ARGV ให้กับอินสแตนซ์ var @default_argv สำหรับการประมวลผลในภายหลัง - ARGV จริง ๆ ควรเป็นพารามิเตอร์กระตุ้นการแยกวิเคราะห์และการใช้ซ้ำตามความเหมาะสม การกำหนดพารามิเตอร์ที่เหมาะสมด้วยค่าเริ่มต้นที่เหมาะสม (พูด ARGV) จะหลีกเลี่ยงความต้องการที่จะเปลี่ยน ARGV "คงที่" เพียงแค่ 2 worth - คิดมาก ...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.