Является ли "eval" единственным способом взаимодействия с объектами привязки в Ruby?
Я новичок в Ruby, и до сих пор выяснение того, как использовать "привязывающие" объекты, является одной из самых больших болевых точек для меня. Если я правильно читаю документацию, они почти полностью непрозрачны. Чтобы получить доступ к области внутри объекта привязки, вы должны иметь строку кода Ruby и eval, используя привязку.
Может быть, я просто пурист из другой школы, но у меня аллергия на строковые конструкции "eval", вообще говоря. Есть ли способ сделать одно из следующего, надежно и в общем случае, с учетом объекта привязки:
- Перечислите идентификаторы в области видимости в контексте, представляющем привязку, или получите хэш содержимого.
- Задайте значение локальной переменной в привязке, равную значению некоторой локальной переменной во внешнем контексте. В идеале это должно работать в целом, даже если значение является ссылкой на объект, файловым дескриптором или каким-либо другим сложным объектом.
- (extension 2:) Если задан хэш, задайте locals в привязке для каждой записи.
- Еще лучше, учитывая хэш, строит привязку только с базовыми языковыми конструкциями и именами в хэш-области.
В принципе, я хочу знать, какая из них возможна, и как выполнить те, которые есть. Я полагаю, что решения для каждого из них будут довольно тесно связаны, поэтому я ставил все это в один вопрос.
В качестве альтернативы, существует ли какой-либо способ eval-кода, который уже был проанализирован в контексте привязки, подобно синтаксису BLL-кода Perl eval?
Ответы
Ответ 1
В поисках больше я нашел ответ хотя бы на часть моего вопроса:
На основе: http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print
Остальное - от экспериментов после полезных подсказок Джима Шуберта.
- Это может быть выполнено
eval
-ing local_variables
, instance_variables
и global_variables
внутри привязки.
- Вы можете сделать что-то, как описано ниже, с учетом
var_name
, new_val
, my_binding
(синтаксис может быть несовершенным или улучшаемым, не стесняйтесь предлагать в комментариях. Кроме того, я не мог заставить форматирование кода работать внутри списка также будут реализованы предложения о том, как это сделать.)
- Вы можете прямо взять (2) и зациклировать хеш для этого.
- См. второй блок кода ниже. Идея состоит в том, чтобы начать с TOPLEVEL_BINDING, который, как я считаю, обычно включает в себя глобальные переменные.
Это связано с использованием строки eval
. Однако никакие значения переменных никогда не расширяются в вовлеченные строки, поэтому они должны быть достаточно безопасными, если их использовать, как описано, и должны работать, чтобы "передавать" значения комплексных переменных.
Также обратите внимание, что всегда можно сделать eval var_name, my_binding
, чтобы получить значение переменной. Обратите внимание, что во всех этих видах жизненно важно, чтобы имя переменной было безопасным для eval, поэтому в идеале оно не должно происходить из любого пользовательского ввода вообще.
Установка переменной внутри привязки заданной var_name
, new_val
, my_binding
:
# the assignment to nil in the eval coerces the variable into existence at the outer scope
setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding
setter.call(new_val)
Создание привязки "на заказ":
my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding
# set_in_binding is based on the above snippet
vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) }
Ответ 2
Уолтер, вы должны иметь возможность напрямую взаимодействовать с привязкой. Я раньше не работал с привязками, но я запустил пару вещей в irb:
[email protected]:~> irb
irb(main):001:0> eval "self", TOPLEVEL_BINDING
=> main
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING
=> []
irb(main):003:0> eval "methods", TOPLEVEL_BINDING
=> ["irb_kill", "inspect", "chws", "install_alias_method", ....
У меня также есть Metaprogramming Ruby, который не говорит о привязке. Однако, если вы заберете это, в конце страницы 144 говорится:
В каком-то смысле вы можете видеть привязку объекты как "более чистые" формы замыканий чем блоки, потому что эти объекты содержат область действия, но не содержат код.
И на противоположной странице предлагается использовать код irb (удаление последних двух аргументов для вызова eval), чтобы увидеть, как он использует привязки:
//ctwc/irb/workspace.rb
eval (statements, @binding) #, file, line)
И... Я собирался предложить пропустить лямбду, но я вижу, что вы просто ответили на это, поэтому я оставлю irb winkering в качестве предложения для дальнейших исследований.
Ответ 3
Не могли бы вы объяснить, что именно вы пытаетесь сделать? Просьба представить код, показывающий, как вы хотите, чтобы он работал. Там может быть лучший и безопасный способ выполнить то, что вы хотите.
Я собираюсь сделать попытку угадать ваш типичный случай использования.
Учитывая хеш:
{: a = > 11,: b = > 22}
Вам нужна минимальная, относительно изолированная среда исполнения, в которой вы можете получить доступ к значениям хэша в качестве локальных переменных. (Я не совсем уверен, почему вам нужны они, чтобы быть местными, за исключением, может быть, если вы пишете DSL, или если у вас уже написанный код, который вы не хотите адаптировать для доступа к Hash.)
Здесь моя попытка. Для простоты я предполагаю, что вы используете символы только как хэш-ключи.
class MyBinding
def initialize(varhash); @vars=varhash; end
def method_missing(methname, *args)
meth_s = methname.to_s
if meth_s =~ /=\z/
@vars[meth_s.sub(/=\z/, '').to_sym] = args.first
else
@vars[methname]
end
end
def eval(&block)
instance_eval &block
end
end
Пример использования:
hash = {:a => 11, :b => 22}
mb = MyBinding.new hash
puts mb.eval { a + b }
# setting values is not as natural:
mb.eval { self.a = 33 }
puts mb.eval { a + b }
Некоторые оговорки:
1) Я не повышал NameError, если переменная не существовала, но простые исправления для замещения, которые:
def initialize(varhash)
@vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
@vars.update(varhash)
end
2) Нормальные правила определения области видимости таковы, что если локальный существует, имя которого совпадает с именем метода, то локаль имеет приоритет, если вы явно не вызываете вызов метода, например a(). Класс выше имеет противоположное поведение; метод имеет приоритет. Чтобы получить "нормальное" поведение, вам нужно скрыть все (или большинство) стандартных методов, например #object_id. ActiveSupport предоставляет для этой цели класс BlankSlate; он поддерживает только 3 метода:
__send__, instance_eval, __id__
. Чтобы использовать его, просто сделайте MyBinding наследованием от BlankSlate.
Помимо этих 3 методов, вы также не сможете иметь локальных жителей с именем "eval" или "method_missing".
3) Настройка локального не так естественно, потому что для получения вызова метода требуется "я". Не уверен, есть ли способ для этого.
4) Блок eval может испортиться с хэшем @vars.
5) Если у вас есть реальный локальный var в области, где вы называете mb.eval, с тем же именем, что и один из хэш-ключей, реальный локальный будет иметь приоритет... это, вероятно, самый большой недостаток, потому что тонкий ошибки могут закрашиваться.
После реализации недостатков моей техники я рекомендую использовать Hash напрямую, чтобы сохранить набор переменных, если только я не вижу причин иначе. Но если вы все еще хотите использовать собственный eval, вы можете повысить безопасность с помощью Regexps во избежание ввода кода, а "локальная" настройка $SAFE будет выше для eval-вызова с помощью Proc, например:
proc { $SAFE = 1; eval "do_some_stuff" }.call # returns the value of eval call
Ответ 4
Здесь приведен код для решения на основе Hash.
class ScopedHash
def initialize(varhash)
# You can use an OpenStruct instead of a Hash, but the you lose the NameError feature.
# OpenStructs also don't have the ability to list their members unless you call a protected method
@vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
@vars.update(varhash)
end
def eval
yield @vars
end
end
if __FILE__ == $0
# sample usage
hash = {:a => 11, :b => 22}
sh = ScopedHash.new hash
puts sh.eval {|v| v[:a] + v[:b] }
sh.eval {|v| v[:a] = 33 }
puts sh.eval {|v| v[:a] + v[:b] }
sh.eval{|v| v[:c] } # raises NameError
end
Итак, вместо использования локальных жителей вы просто получите доступ к полученному Hash. Я думаю, что существует очень мало причин, по которым человек будет вынужден манипулировать объектом Binding; обычно есть более чистые способы выполнения задачи.