代码之家  ›  专栏  ›  技术社区  ›  Mike Stone

我如何才能从方法中获取绑定丢失?

  •  3
  • Mike Stone  · 技术社区  · 15 年前

    我试图找到一种方法来从Ruby(1.8)中缺少的方法内的调用者那里获取绑定,但是我似乎找不到一种方法来实现它。

    希望下面的代码能够解释我想要做什么:

    class A
      def some_method
        x = 123
        nonexistent_method
      end
    
      def method_missing(method, *args, &block)
        b = caller_binding # <---- Is this possible?
        eval "puts x", b
      end
    end
    
    A.new.some_method
    # expected output:
    #   123
    

    所以…有没有办法获得调用者的绑定,或者这在Ruby(1.8)中是不可能的?

    3 回复  |  直到 15 年前
        1
  •  6
  •   rampion    15 年前

    这是一个(有些脆弱的)黑客:

    # caller_binding.rb
    TRACE_STACK = []
    VERSION_OFFSET = { "1.8.6" => -3, "1.9.1" => -2 }[RUBY_VERSION]
    def caller_binding(skip=1)
      TRACE_STACK[ VERSION_OFFSET - skip ][:binding]
    end
    set_trace_func(lambda do |event, file, line, id, binding, classname|
      item = {:event=>event,:file=>file,:line=>line,:id=>id,:binding=>binding,:classname=>classname}
      #p item
      case(event)
      when 'line'
        TRACE_STACK.push(item) if TRACE_STACK.empty?
      when /\b(?:(?:c-)?call|class)\b/
        TRACE_STACK.push(item)
      when /\b(?:(?:c-)?return|end|raise)\b/
        TRACE_STACK.pop
      end
    end)
    

    这适用于您的示例,但我没有用其他很多方法来测试它。

    require 'caller_binding'
    class A
      def some_method
        x = 123
        nonexistent_method
      end
      def method_missing( method, *args, &block )
        b = caller_binding
        eval "puts x", b
      end
    end
    
    x = 456
    A.new.some_method #=> prints 123
    A.new.nonexistent_method #=> prints 456
    

    当然,如果绑定没有定义您要评估的变量,这将不起作用,但这是绑定的一般问题。如果一个变量没有定义,它就不知道它是什么。

    require 'caller_binding'
    def show_x(b)
      begin
        eval <<-SCRIPT, b
          puts "x = \#{x}"
        SCRIPT
      rescue => e
        puts e
      end
    end
    
    def y
      show_x(caller_binding)
    end
    
    def ex1
      y #=> prints "undefined local variable or method `x' for main:Object"
      show_x(binding) #=> prints "undefined local variable or method `x' for main:Object"
    end
    
    def ex2
      x = 123
      y #+> prints "x = 123"
      show_x(binding) #+> prints "x = 123"
    end
    
    ex1
    ex2
    

    要解决此问题,需要在计算的字符串中执行一些错误处理:

    require 'caller_binding'
    def show_x(b)
      begin
        eval <<-SCRIPT, b
          if defined? x
            puts "x = \#{x}"
          else
            puts "x not defined"
          end
        SCRIPT
      rescue => e
        puts e
      end
    end
    
    def y
      show_x(caller_binding)
    end
    
    def ex1
      y #=> prints "x not defined"
      show_x(binding) #=> prints "x not defined"
    end
    
    def ex2
      x = 123
      y #+> prints "x = 123"
      show_x(binding) #+> prints "x = 123"
    end
    
    ex1
    ex2
    
        2
  •  3
  •   sepp2k    15 年前

    如果使用块调用该方法,则可以通过执行以下操作获取块的绑定(该绑定在调用方的绑定上关闭) block.binding . 不过,如果没有一个街区的话,那是行不通的。

    您不能直接获得调用方的绑定(当然,除非您明确地传递它)。

    编辑:我应该补充一点,曾经有一个binding.of撎caller方法在周围浮动,但它不再适用于任何最新的Ruby版本(其中最新的版本包括1.8.6)。

        3
  •  2
  •   Chris Bunch    15 年前

    这可能比你想的要麻烦,但我有一个办法可以做到。

    #x = 1 # can uncomment out this and comment the other if you like
    
    A = Class.new do
      x = 1
      define_method :some_method do
        x = 123
        nonexistent_method
      end
    
      define_method :method_missing do |method, *args|
        puts x
      end
    end
    
    A.new.some_method
    

    将类和方法定义替换为 Class.new define_method 不过,打电话只是工作的一半。不幸的是,丑陋的部分是,只有当你已经定义了 x 事先,这样您就不会真正抓取调用者的绑定(相反,被调用者在不同的范围内修改变量)。

    这可能等同于将所有变量定义为全局变量,但这可能对您有所帮助,具体取决于您的情况。也许有了这个改变,你就能找到谜题的最后一块了(如果这对你不起作用的话)。

    编辑: 您可以按如下方式获得任何方法的绑定,但即使使用它,我也无法 eval 成功(一定要把这个放在最上面)。这个会加满的 @@binding 与的绑定 some_method method_missing (按这个顺序),所以也许这能有所帮助。

    @@binding = []
    
    class Class
      alias real_def define_method
      def define_method(method_name, &block)
        real_def method_name, &block
        @@binding << block.binding
      end
    end