【问题标题】:Can you eval code in the context of a caller in Ruby?你能在 Ruby 调用者的上下文中评估代码吗?
【发布时间】:2010-11-24 07:30:28
【问题描述】:

基本上我想知道the following 是否可以在Ruby 中完成。

例如:

def bar(symbol) 
  # magic code goes here, it outputs "a = 100" 
end

def foo
  a = 100 
  bar(:a) 
end

【问题讨论】:

    标签: ruby


    【解决方案1】:

    您必须将foo 的上下文传递给bar

    def foo
      a = 100
      bar(:a, binding)
    end
    def bar(sym, b)
      puts "#{sym} is #{eval(sym.to_s, b)}"
    end
    

    【讨论】:

      【解决方案2】:

      在 1.8.X 或 1.9.X 的 Ruby 中,没有内置方法来获取调用者绑定

      您可以使用https://github.com/banister/binding_of_caller 来解决。

      在 MRI 2.0 中,您可以使用 RubyVM::DebugInspector,请参阅:https://github.com/banister/binding_of_caller/blob/master/lib/binding_of_caller/mri2.rb

      MRI 2.0 中的工作样本:

      require 'debug_inspector'
      
      def bar(symbol)
        RubyVM::DebugInspector.open do |inspector|
          val = eval(symbol.to_s, inspector.frame_binding(2))
          puts "#{symbol}: #{val}"
        end
      end
      
      def foo
        a = 100
        bar(:a)
      end
      
      foo
      # a: 100
      

      【讨论】:

      • 这里有一个使用 set_trace_func 的脆弱方式的例子:stackoverflow.com/questions/1314592/…
      • 应包含免责声明:请不要这样做,除非您实际上使用它进行某种调试。
      • 为什么选择@tmandry?这是一种元编程技术——它是不受欢迎的,因为所有元编程都是不受欢迎的(难以维护和调试),但它并不危险,是一种新的或特殊的方式。
      【解决方案3】:

      这是一个更简单的语法破解,使用传入的块绑定:

        def loginfo &block
          what = yield.to_s
          evaled = eval(what, block.binding)
          Rails.logger.info "#{what} = #{evaled.inspect}"
        end
      

      这样称呼:

        x = 1
        loginfo{ :x }
      

      将退出:

        x = 1
      

      【讨论】:

        【解决方案4】:

        仅供参考,这是一种“骇人听闻的方式”。 这是我对众所周知的 ppp.rb 的(重新)实现:

        #!/usr/bin/ruby
        #
        # better ppp.rb
        #
        
        require 'continuation' if RUBY_VERSION >= '1.9.0'
        
        def ppp(*sym)
          cc = nil
          ok = false
        
          set_trace_func lambda {|event, file, lineno, id, binding, klass|
            if ok
              set_trace_func nil
              cc.call(binding)
            else
              ok = event == "return"
            end
          }
          return unless bb = callcc{|c| cc = c; nil }
        
          sym.map{|s| v = eval(s.to_s, bb); puts "#{s.inspect} = #{v}"; v }
        end
        
        a = 1
        s = "hello"
        ppp :a, :s
        
        exit 0
        

        目前在 1.9 中失败。[012]由于 ruby​​ 的 set_trace_func 中的错误。

        【讨论】:

        【解决方案5】:

        查看文章Variable Bindings in Ruby

        class Reference
          def initialize(var_name, vars)
            @getter = eval "lambda { #{var_name} }", vars
            @setter = eval "lambda { |v| #{var_name} = v }", vars
          end
          def value
            @getter.call
          end
          def value=(new_value)
            @setter.call(new_value)
          end
        end
        
        def ref(&block)
          Reference.new(block.call, block.binding)
        end
        
        def bar(ref)
          # magic code goes here, it outputs "a = 100" 
          p ref.value
        end
        
        def foo
          a = 100 
          bar(ref{:a}) 
        end
        
        foo
        

        【讨论】:

        • +1 看起来很有趣,我想知道这是否可以在没有块的情况下完成...
        • 我认为没有 ruby​​ 1.8.x 中的块就无法完成。我对 ruby​​ 1.9.x 不熟悉,所以那里的情况可能有所不同。
        • 在 Ruby 2.1 中你仍然需要这个块,但是 Reference 的实现可以通过调用 Binding#local_variable_getBinding#vars.local_variable_set 来重做。虽然,当前的实现可能除了局部变量之外还允许调用读取器和写入器方法,而新的实现不会...
        猜你喜欢
        • 2015-05-29
        • 2021-02-14
        • 2013-01-13
        • 2012-10-13
        • 2014-10-16
        • 2016-04-17
        • 2011-03-17
        • 1970-01-01
        • 2012-02-10
        相关资源
        最近更新 更多