Подтвердить что ты не робот

Как динамически создавать локальную переменную?

У меня есть переменная 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.
4b9b3361

Ответ 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