2013-08-31 33 views
21

Tôi có một biến số var = "some_name" và tôi muốn tạo một đối tượng mới và gán nó cho some_name. Tôi làm nó như thế nào? Ví dụ.Cách tạo động một biến cục bộ?

var = "some_name" 
some_name = Struct.new(:name) # I need this 
a = some_name.new('blah') # so that I can do this. 
+0

'" some_name "' và 'some_name' khác nhau, một đối tượng và một biến cục bộ khác là –

+0

đúng. Tôi muốn sử dụng giá trị của 'var' như là toán hạng bên trái trong Struct.new – Bala

+0

Có thể bạn đang nghĩ về những thứ như Perl hoặc PHP, trong đó' $ x = 'y'' và '$$ x =' z'' có nghĩa là cũng giống như '$ y = 'z''. Không có sự tương đương cụ thể nào trong Ruby, vì các tham chiếu biến không tồn tại trên mỗi se. – tadman

Trả lời

30

Bạn có thể không tự động tạo ra các biến địa phương trong Ruby 1.9+ (bạn có thể trong Ruby 1.8 qua eval):

eval 'foo = "bar"' 
foo # NameError: undefined local variable or method `foo' for main:Object 

Chúng có thể được sử dụng trong mã eval-ed bản thân, mặc dù:

eval 'foo = "bar"; foo + "baz"' 
#=> "barbaz" 

Ruby 2.1 thêm local_variable_set, nhưng điều đó không thể tạo biến cục bộ mới:

binding.local_variable_set :foo, 'bar' 
foo # NameError: undefined local variable or method `foo' for main:Object 

Hành vi này không thể thay đổi mà không sửa đổi bản thân Ruby. Thay vào đó, thay vào đó, hãy xem xét lưu trữ dữ liệu của bạn trong cấu trúc dữ liệu khác, ví dụ: một Hash, thay vì nhiều biến cục bộ:

hash = {} 
hash[:my_var] = :foo 

Lưu ý rằng cả hai evallocal_variable_setlàm phép reassigning một biến địa phương hiện có:

foo = nil 
eval 'foo = "bar"' 
foo #=> "bar" 
binding.local_variable_set :foo, 'baz' 
foo #=> "baz" 
+3

Vui lòng đọc đoạn cuối. Nếu bạn nghĩ rằng bạn cần phải tự động tạo hoặc đọc từ các biến, đặc biệt là các biến địa phương, nó thường là một dấu hiệu cho thấy bạn không sử dụng các cấu trúc dữ liệu chính xác. – Phrogz

+2

Ruby 2.1 giới thiệu local_variable_set, local_variable_get và local_variable_defined? về Binding –

+1

Về mặt kỹ thuật, bạn có thể tạo các biến cục bộ, nhưng chúng cục bộ với ngữ cảnh 'eval'. Bạn không thể đẩy chúng lên đến bối cảnh gốc. – tadman

2

Mặc dù, như những người khác đã chỉ ra, bạn không thể tạo động các biến cục bộ trong Ruby, bạn có thể mô phỏng hành vi này ở một mức độ nào đó bằng các phương thức:

hash_of_variables = {var1: "Value 1", var2: "Value 2"} 

hash_of_variables.each do |var, val| 
    define_method(var) do 
    instance_variable_get("@__#{var}") 
    end 
    instance_variable_set("@__#{var}", val) 
end 

puts var1 
puts var2 
var1 = var2.upcase 
puts var1 

Prints:

Value 1 
Value 2 
VALUE 2 

Một số thư viện kết hợp kỹ thuật này với instance_exec để lộ những gì dường như biến cục bộ bên trong một khối:

def with_vars(vars_hash, &block) 
    scope = Object.new 
    vars_hash.each do |var, val| 
    scope.send(:define_singleton_method, var) do 
     scope.instance_variable_get("@__#{var}") 
    end 
    scope.instance_variable_set("@__#{var}", val) 
    end 
    scope.instance_exec(&block) 
end 

with_vars(a: 1, b:2) do 
    puts a + b 
end 

In: 3

Hãy nhận biết rằng mặc dù trừu tượng là hoàn toàn không có nghĩa là:

with_vars(a: 1, b:2) do 
    a = a + 1 
    puts a 
end 

Kết quả bằng: undefined method `+' for nil:NilClass. Điều này là do a= xác định biến cục bộ thực tế, được khởi tạo thành nil, được ưu tiên hơn phương thức a. Sau đó, a.+(1) được gọi và nil không có phương thức +, do đó, lỗi được đưa ra. Vì vậy, mặc dù phương pháp này khá hữu ích để mô phỏng các biến cục bộ chỉ đọc, nó không phải lúc nào cũng hoạt động tốt khi bạn cố gắng gán lại biến trong khối.

5

Nói về ruby ​​2.2.x đúng là bạn không thể tạo biến cục bộ theo ngữ cảnh trong ngữ cảnh/ràng buộc hiện tại .. nhưng bạn có thể đặt biến trong một số ràng buộc cụ thể mà bạn có tay cầm.

b = binding 
b.local_variable_set :gaga, 5 
b.eval "gaga" 
=> 5 

Thú vị ở đây là gọi cho mỗi lần ràng buộc mới binding. Vì vậy, bạn cần phải có được một xử lý của các ràng buộc bạn quan tâm và sau đó eval trong bối cảnh của nó một khi các biến mong muốn được thiết lập.

Điều này hữu ích như thế nào? Ví dụ tôi muốn đánh giá ERB và viết ERB là đẹp hơn nhiều nếu bạn có thể sử dụng <%= myvar %> thay vì <%= opts[:myvar] %> hoặc một cái gì đó tương tự.

Để tạo liên kết mới, tôi đang sử dụng mô-đun phương thức (Tôi chắc chắn ai đó sẽ sửa tôi cách gọi đúng cách này, trong java tôi gọi đó là phương pháp tĩnh) với các biến đặc biệt thiết lập:

module M 
    def self.clean_binding 
    binding 
    end 

    def self.binding_from_hash(**vars) 
    b = self.clean_binding 
    vars.each do |k, v| 
     b.local_variable_set k.to_sym, v 
    end 
    return b 
    end 
end 
my_nice_binding = M.binding_from_hash(a: 5, **other_opts) 

Bây giờ bạn có một ràng buộc với chỉ các biến mong muốn. Bạn có thể sử dụng nó để đánh giá kiểm soát tốt hơn của ERB hoặc khác (có thể là bên thứ ba) được tin cậy mã (đây là không phải bất kỳ loại sandbox nào). Nó giống như việc định nghĩa một giao diện.

cập nhật: Một vài lưu ý bổ sung về liên kết. Nơi bạn tạo chúng cũng ảnh hưởng đến tính khả dụng của các phương thức và độ phân giải hằng số. Trong ví dụ trên, tôi tạo ra một ràng buộc sạch sẽ hợp lý. Nhưng nếu tôi muốn cung cấp các phương thức thể hiện của một số đối tượng, tôi có thể tạo một ràng buộc bằng một phương thức tương tự nhưng trong lớp của đối tượng đó. ví dụ.

module MyRooTModule 
    class Work 
    def my_instance_method 
     ... 
    end 
    def not_so_clean_binding 
     binding 
    end 
    end 
    class SomeOtherClass 
    end 
end 

Bây giờ tôi my_object.not_so_clean_binding sẽ cho phép mã để gọi #my_instance_method trên my_object đối tượng. Trong cùng một cách, bạn có thể gọi ví dụ SomeOtherClass.new bằng mã bằng cách sử dụng liên kết này thay vì MyRootModule::SomeOtherClass.new. Vì vậy, đôi khi cần cân nhắc nhiều hơn khi tạo một ràng buộc hơn là chỉ các biến cục bộ. HTH

2

Đúng là những gì người khác viết rằng bạn không thể khai báo động đúng biến trong ngữ cảnh cục bộ. Tuy nhiên bạn có thể đạt được chức năng tương tự với các thuộc tính đối tượng và vì trong thế giới Ruby mọi thứ đều là một đối tượng (thậm chí bối cảnh chính), bạn có thể dễ dàng mở rộng các đối tượng đó bằng các thuộc tính mới. Của corse, hoạt động này có thể được thực hiện tự động. Hãy xem xét phương pháp này.

Trước tiên, hãy nhìn vào phạm vi chính với irb.

> self 
=> main 
> self.class 
=> Object 
> self.class.ancestors 
=> [Object, Kernel, BasicObject] 

Như bạn có thể thấy bây giờ, main thực sự là một đối tượng. Đối tượng có thể có các thuộc tính có cùng thuộc tính indirection làm biến. Thông thường, khi khai báo lớp mới chúng ta sẽ sử dụng phương thức attr_accessor nhưng main đã là một đối tượng khởi tạo do đó chúng ta không thể khai báo trực tiếp các thuộc tính mới. Ở đây mixins mô-đun đến để giải cứu.

variable_name = 'foo' 
variable_value = 'bar' 

variable_module = Module.new do 
    attr_accessor variable_name.to_sym 
end 

include variable_module 

instance_variable_set("@#{variable_name}", variable_value) 

p foo # "bar" 

self.foo = 'bad' 

p foo # "baz" 

self.class.ancestors 
# [Object, #<Module:0x007f86cc073aa0>, Kernel, BasicObject] 

Bây giờ bạn thấy rằng main đối tượng đã nhiễm độc với module mới mà giới thiệu thuộc tính mới foo. Để kiểm tra thêm, bạn có thể chạy methods để thấy rằng main hiện có hai phương thức khác là foofoo=.

Để đơn giản hóa hoạt động này, tôi đã viết metaxa đá quý mà tôi rất khuyến khích bạn xem. Đây là ví dụ về cách sử dụng nó.

require 'metaxa' 

include Metaxa 

introduce :foo, with_value: 'foo' 

puts foo == 'foo' # true 
puts foo === get(:foo) # true 

set :foo, 'foobar' 

puts foo == 'foobar' # true 
puts foo === get(:foo) # true 

self.foo = 'foobarbaz' 

puts foo == 'foobarbaz' # true 
puts foo === get(:foo) # true  
Các vấn đề liên quan