Как динамически создавать локальную переменную?
У меня есть переменная var = "some_name"
, и я хотел бы создать новый объект и назначить его some_name
. Как мне это сделать? Например.
var = "some_name"
some_name = Struct.new(:name) # I need this
a = some_name.new('blah') # so that I can do this.
Ответы
Ответ 1
Вы не можете динамически создавать локальные переменные в Ruby 1.9+ (вы могли бы в Ruby 1.8 через eval
):
eval 'foo = "bar"'
foo # NameError: undefined local variable or method `foo' for main:Object
Они могут использоваться внутри самого измененного кода:
eval 'foo = "bar"; foo + "baz"'
#=> "barbaz"
Ruby 2.1 добавил local_variable_set
, но также не может создавать новые локальные переменные:
binding.local_variable_set :foo, 'bar'
foo # NameError: undefined local variable or method `foo' for main:Object
Это поведение нельзя изменить без изменения самого Ruby. Альтернативой является вместо этого рассмотреть возможность хранения ваших данных в другой структуре данных, например. хеш, вместо многих локальных переменных:
hash = {}
hash[:my_var] = :foo
Обратите внимание, что оба eval
и local_variable_set
позволяют переназначить существующую локальную переменную:
foo = nil
eval 'foo = "bar"'
foo #=> "bar"
binding.local_variable_set :foo, 'baz'
foo #=> "baz"
Ответ 2
Говоря о ruby 2.2.x, верно, что вы не можете создавать локальные переменные программно в текущем контексте/привязке. но вы можете установить переменные в каком-то конкретном связывании, у вас есть дескриптор.
b = binding
b.local_variable_set :gaga, 5
b.eval "gaga"
=> 5
Интересно, что вызовы binding
дают вам новое связывание каждый раз. Таким образом, вам нужно получить дескриптор привязки, который вас интересует, а затем eval в нем контекста, когда будут установлены нужные переменные.
Как это полезно? Например, я хочу оценить ERB, и писать ERB намного лучше, если вы можете использовать <%= myvar %>
вместо <%= opts[:myvar] %>
или что-то в этом роде.
Чтобы создать новое связывание, я использую метод класса модуля (я уверен, что кто-то исправит меня, как правильно это назвать, в java, который я бы назвал статическим методом), чтобы получить чистое связывание с определенными переменными набор:
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)
Теперь у вас есть привязка только к нужным переменным. Вы можете использовать его для более удобной контролируемой оценки ERB или другого (возможно, третьего лица) надежного кода (это не песочница любого типа). Это похоже на определение интерфейса.
update: несколько дополнительных заметок о привязках. Место, которое вы создаете, также влияет на доступность методов и разрешение констант. В приведенном выше примере я создаю достаточно чистую привязку. Но если я хочу сделать доступными методы экземпляра какого-либо объекта, я мог бы создать привязку аналогичным методом, но внутри класса этого объекта. например.
module MyRooTModule
class Work
def my_instance_method
...
end
def not_so_clean_binding
binding
end
end
class SomeOtherClass
end
end
Теперь мой my_object.not_so_clean_binding
позволит коду вызвать #my_instance_method
на my_object
объект. Точно так же вы можете вызвать, например, SomeOtherClass.new
в коде, используя эту привязку вместо MyRootModule::SomeOtherClass.new
. Поэтому при создании привязки иногда требуется больше внимания, чем просто локальные переменные. НТН
Ответ 3
Хотя, как указывали другие, вы не можете динамически создавать локальные переменные в Ruby, вы можете в некоторой степени имитировать это поведение с помощью методов:
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
Печать
Value 1
Value 2
VALUE 2
Некоторые библиотеки объединяют этот метод с instance_exec
, чтобы выявить то, что кажется локальными переменными внутри блока:
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
Отпечатки: 3
Помните, что абстракция отнюдь не идеальна:
with_vars(a: 1, b:2) do
a = a + 1
puts a
end
Результаты: undefined method `+' for nil:NilClass
. Это связано с тем, что a=
определяет фактическую локальную переменную, инициализированную на nil
, которая имеет приоритет над методом a
. Затем вызывается a.+(1)
, а nil
не имеет метода +
, поэтому возникает ошибка.
Поэтому, хотя этот метод очень полезен для моделирования локальных переменных только для чтения, он не всегда работает хорошо, когда вы пытаетесь переназначить переменную внутри блока.
Ответ 4
Верно, что другие писали, что вы не можете динамически объявлять истинную переменную в локальном контексте. Однако вы можете достичь аналогичной функциональности с атрибутами объекта, и поскольку в мире Ruby все является объектом (даже основным контекстом), вы можете легко расширить эти объекты новыми атрибутами. Корс, эта операция может быть выполнена динамически. Давайте рассмотрим этот подход.
Во-первых, рассмотрим основной объем с помощью irb
.
> self
=> main
> self.class
=> Object
> self.class.ancestors
=> [Object, Kernel, BasicObject]
Как вы теперь видите, main
- это действительно объект. Объекты могут иметь атрибуты, которые имеют одинаковое свойство indirection как переменные. Обычно при объявлении нового класса мы будем использовать метод attr_accessor
, но main
уже является экземпляром объекта, поэтому мы не можем напрямую объявлять новые атрибуты. Здесь модуль mixins подходит для спасения.
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]
Теперь вы видите, что объект main
был испорчен новым модулем, который ввел новый атрибут foo
. Для дальнейшей проверки вы можете запустить methods
, чтобы увидеть, что main
теперь имеют еще два метода foo
и foo=
.
Чтобы упростить эту операцию, я написал metaxa gem, который я настоятельно рекомендую вам проверить. Это пример того, как его использовать.
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