Phân công động


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

cho tôi lỗi:

Cú pháp: lỗi gán hằng số động

Tại sao điều này được coi là một hằng số động? Tôi chỉ gán một chuỗi cho nó.


34
Năng động liên tục là một cái gì đó giống như nước khô? :)
fl00r

39
Nó không nói rằng hằng số là động. Nó nói rằng sự phân công là năng động.
sepp2k

Câu trả lời:


141

Vấn đề của bạn là mỗi lần bạn chạy phương thức bạn đang gán một giá trị mới cho hằng số. Điều này không được phép, vì nó làm cho hằng số không đổi; mặc dù nội dung của chuỗi là như nhau (hiện tại, dù sao đi nữa), bản thân đối tượng chuỗi thực tế sẽ khác nhau mỗi khi phương thức được gọi. Ví dụ:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Có lẽ nếu bạn giải thích trường hợp sử dụng của bạn, tại sao bạn muốn thay đổi giá trị của hằng số trong một phương thức thì chúng tôi có thể giúp bạn thực hiện tốt hơn.

Có lẽ bạn muốn có một biến đối tượng trên lớp?

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"

Nếu bạn thực sự muốn thay đổi giá trị của hằng số trong một phương thức và hằng số của bạn là Chuỗi hoặc Mảng, bạn có thể 'gian lận' và sử dụng #replacephương thức để khiến đối tượng nhận một giá trị mới mà không thực sự thay đổi đối tượng:

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 không bao giờ nói rằng anh ấy muốn thay đổi giá trị của hằng số mà chỉ muốn gán giá trị. Trường hợp sử dụng thường xuyên dẫn đến lỗi Ruby này là khi bạn xây dựng giá trị trong một phương thức từ các tài sản thời gian chạy khác (biến, đối số dòng lệnh, ENV), thường là trong một hàm tạo, ví dụ def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. Đó là một trong những trường hợp mà Ruby không có cách đơn giản.
Arnaud Meuret

2
@ArnaudMeuret Trong trường hợp đó, bạn muốn một biến đối tượng (ví dụ @variable), không phải là hằng số. Nếu không, bạn sẽ được chỉ định lại DBmỗi khi bạn khởi tạo một thể hiện mới của lớp đó.
Ajedi32

2
@ Ajedi32 Tình huống này thường phát sinh từ các ràng buộc bên ngoài không phải là các lựa chọn thiết kế như ví dụ của tôi với Sequel. Quan điểm của tôi là việc gán giá trị cho hằng số được Ruby cho phép trong phạm vi nhất định chứ không phải cho các phạm vi khác. Nó được sử dụng để nhà phát triển lựa chọn một cách khôn ngoan khi thực hiện nhiệm vụ. Ruby đã thay đổi về điều này. Không phải vì lợi ích của mọi người.
Arnaud Meuret

2
@ArnaudMeuret Tôi thừa nhận tôi chưa bao giờ sử dụng Sequel trước đây vì vậy tôi không thể nói điều này với sự chắc chắn 100%, nhưng chỉ cần liếc qua tài liệu cho Sequel tôi không thấy gì mà bạn phải gán kết quả Sequel.connectcho một hằng số có tên DB . Trong thực tế, tài liệu nói rõ ràng rằng đó chỉ là một khuyến nghị. Điều đó không giống như một ràng buộc bên ngoài đối với tôi.
Ajedi32

@ Ajedi32 1) Tôi chưa bao giờ viết rằng (tên của hằng số hoặc thậm chí là bạn phải giữ nó ở đâu đó) nó chỉ là một ví dụ 2) Hạn chế là phần mềm của bạn có thể không có thông tin cần thiết cho đến khi bạn thường ở trong bối cảnh động .
Arnaud Meuret

69

Vì các hằng số trong Ruby không có nghĩa là phải thay đổi, Ruby không khuyến khích bạn gán cho chúng trong các phần của mã có thể được thực thi nhiều lần, chẳng hạn như các phương thức bên trong.

Trong trường hợp bình thường, bạn nên định nghĩa hằng số bên trong lớp:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Nếu vì một lý do nào đó mặc dù bạn thực sự cần xác định một hằng trong một phương thức (có lẽ đối với một số loại siêu lập trình), bạn có thể sử dụng 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"

Một lần nữa, const_set không phải là thứ bạn thực sự phải dùng đến trong những trường hợp bình thường. Nếu bạn không chắc chắn liệu bạn có thực sự muốn gán cho hằng số theo cách này hay không, bạn có thể muốn xem xét một trong những lựa chọn thay thế sau:

Biến lớp

Các biến lớp hoạt động như hằng số theo nhiều cách. Chúng là các thuộc tính trên một lớp và chúng có thể truy cập được trong các lớp con của lớp mà chúng được định nghĩa.

Sự khác biệt là các biến lớp có nghĩa là có thể sửa đổi và do đó có thể được gán cho các phương thức bên trong mà không có vấn đề gì.

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"

Thuộc tính lớp

Các thuộc tính lớp là một loại "biến thể hiện trên một lớp". Chúng hoạt động hơi giống các biến lớp, ngoại trừ giá trị của chúng không được chia sẻ với các lớp con.

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"

Biến thể hiện

Và để hoàn thiện, có lẽ tôi nên đề cập: nếu bạn cần gán một giá trị chỉ có thể được xác định sau khi lớp của bạn được khởi tạo, có nhiều khả năng bạn thực sự đang tìm kiếm một biến thể cũ đơn giản.

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

Trong Ruby, bất kỳ biến nào có tên bắt đầu bằng chữ in hoa là một hằng số và bạn chỉ có thể gán cho nó một lần. Chọn một trong những lựa chọn thay thế sau:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end

2
Cảm ơn chúa vì ai đó đã đề cập rằng "bất kỳ biến nào có tên bắt đầu bằng chữ in hoa là một hằng số!"
ubienewbie


0

Bạn không thể đặt tên một biến bằng chữ in hoa hoặc Ruby sẽ coi đó là hằng số và sẽ muốn nó giữ giá trị của nó không đổi, trong trường hợp thay đổi giá trị của nó sẽ là một lỗi "lỗi gán hằng số động". Với chữ thường nên ổn

class MyClass
  def mymethod
    myconstant = "blah"
  end
end

0

Ruby không thích rằng bạn đang gán hằng số bên trong một phương thức vì nó có nguy cơ gán lại. Một số câu trả lời SO trước khi tôi đưa ra phương án gán nó bên ngoài một phương thức - nhưng trong lớp, đó là nơi tốt hơn để gán nó.


1
Weicome để SO John. Yo có thể xem xét cải thiện câu trả lời này bằng cách thêm một số mã mẫu của những gì bạn đang mô tả.
Cleptus

0

Rất cám ơn Dorian và Phrogz đã nhắc nhở tôi về phương thức mảng (và hàm băm) #replace, có thể "thay thế nội dung của một mảng hoặc hàm băm".

Quan niệm rằng giá trị của CONSTANT có thể được thay đổi, nhưng với một cảnh báo khó chịu, là một trong những bước sai lầm về khái niệm của Ruby - những điều này nên hoàn toàn bất biến, hoặc loại bỏ hoàn toàn ý tưởng không đổi. Từ quan điểm của một lập trình viên, một hằng số là khai báo và cố ý, một tín hiệu cho người khác rằng "giá trị này thực sự không thể thay đổi một khi được khai báo / gán."

Nhưng đôi khi một "tuyên bố rõ ràng" thực sự bị tịch thu các cơ hội hữu ích khác trong tương lai. Ví dụ...

rất trường hợp sử dụng hợp pháp trong đó giá trị "hằng số" có thể thực sự cần phải thay đổi: ví dụ: tải lại ARGV từ vòng lặp giống như REPL, sau đó chạy lại ARGV qua nhiều hơn (tiếp theo) OptionParser.parse! gọi - thì đấy! Cung cấp "dòng lệnh lập luận" một tiện ích động hoàn toàn mới.

Vấn đề thực tế là một trong hai với giả định giả định rằng "argv phải là một hằng số", hoặc trong phương thức khởi riêng optparse, mà cứng mã sự phân công của argv đến dụ var @default_argv cho sau này xử lý - mà mảng (argv) thực sự nên là một tham số, khuyến khích phân tích lại và sử dụng lại, khi thích hợp. Tham số hóa phù hợp, với một mặc định thích hợp (giả sử ARGV) sẽ tránh được việc phải thay đổi ARGV "không đổi". Chỉ cần một vài suy nghĩ xứng đáng ...

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.