Динамически задавать локальные переменные в Ruby
Мне интересно динамически устанавливать локальные переменные в Ruby. Не создавать методы, константы или переменные экземпляра.
Так что-то вроде:
args[:a] = 1
args.each_pair do |k,v|
Object.make_instance_var k,v
end
puts a
> 1
Я хочу локальные переменные именно потому, что этот метод находится в модели, и я не хочу загрязнять глобальное или пространство объектов.
Ответы
Ответ 1
Проблема заключается в том, что блок внутри each_pair имеет разную область. Любые локальные переменные, назначенные в нем, будут доступны только там. Например, это:
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair do |k,v|
key = k.to_s
eval('key = v')
eval('puts key')
end
puts a
Производит следующее:
1
2
undefined local variable or method `a' for main:Object (NameError)
Чтобы обойти это, вы можете создать локальный хеш, назначить ключи для этого хэша и получить к ним доступ так:
args = {}
args[:a] = 1
args[:b] = 2
localHash = {}
args.each_pair do |k,v|
key = k.to_s
localHash[key] = v
end
puts localHash['a']
puts localHash['b']
Конечно, в этом примере он просто копирует исходный хэш со строками для ключей. Я предполагаю, что фактический прецедент, однако, более сложный.
Ответ 2
В качестве дополнительной информации для будущих читателей, начиная с ruby 2.1.0, вы можете использовать binding.local_variable_get
и binding.local_variable_set
:
def foo
a = 1
b = binding
b.local_variable_set(:a, 2) # set existing local variable `a'
b.local_variable_set(:c, 3) # create new local variable `c'
# `c' exists only in binding.
b.local_variable_get(:a) #=> 2
b.local_variable_get(:c) #=> 3
p a #=> 2
p c #=> NameError
end
Как указано в документе, это похоже на поведение
binding.eval("#{symbol} = #{obj}")
binding.eval("#{symbol}")
Ответ 3
интересно, вы можете изменить локальную переменную, но вы не можете установить:
def test
x=3
eval('x=7;')
puts x
end
test = > 7
def test
eval('x=7;')
puts x
end
test = > NameError: undefined локальная переменная или метод `x 'для main: Object
Это единственная причина, по которой работает код Dorkus Prime.
Ответ 4
Я предлагаю вам использовать хэш (но продолжайте читать для других альтернатив).
Почему?
Разрешение произвольных именованных аргументов делает чрезвычайно неустойчивый код.
Скажем, у вас есть метод foo
, который вы хотите принять эти теоретические именованные аргументы.
Сценарии:
-
Вызываемый метод (foo
) должен вызвать частный метод (пусть его вызывает bar
), который не принимает аргументов. Если вы передадите аргумент foo
, который хотите сохранить в локальной переменной bar
, он будет маскировать метод bar
. Обходной путь состоит в том, чтобы иметь явные скобки при вызове bar
.
-
Скажем, foo
код присваивает локальную переменную. Но затем вызывающий решает передать в arg с тем же именем, что и локальная переменная. Назначение будет clobber аргумент.
В принципе, вызывающий метод никогда не сможет изменить логику метода.
Альтернативы
Альтернативная средняя земля включает OpenStruct
. Это менее типизируется, чем использование хэша.
require 'ostruct'
os = OpenStruct.new(:a => 1, :b => 2)
os.a # => 1
os.a = 2 # settable
os.foo # => nil
Обратите внимание, что OpenStruct
позволяет вам получить доступ к несуществующим членам - он вернет nil
. Если вы хотите более строгую версию, используйте Struct
вместо этого.
Это создает анонимный класс, а затем создает экземпляр класса.
h = {:a=>1, :b=>2}
obj = Struct.new(* h.keys).new(* h.values)
obj.a # => 1
obj.a = 2 # settable
obj.foo # NoMethodError
Ответ 5
так как вы не хотите констант
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair{|k,v|eval "@#{k}=#{v};"}
puts @b
2
вы можете найти этот подход интересным (оцените переменные в правильном контексте)
fn="b*b"
vars=""
args.each_pair{|k,v|vars+="#{k}=#{v};"}
eval vars + fn
4