【问题标题】:How to find where a method is defined at runtime?如何找到在运行时定义方法的位置?
【发布时间】:2010-09-15 14:17:28
【问题描述】:

我们最近遇到了一个问题,在发生一系列提交后,后端进程无法运行。现在,我们都是好孩子,每次签入后都会运行 rake test,但由于 Rails 库加载的一些奇怪之处,只有当我们在生产模式下直接从 Mongrel 运行它时才会发生这种情况。

我追踪了这个错误,这是由于一个新的 Rails gem 覆盖了 String 类中的一个方法,这种方式打破了运行时 Rails 代码中的一个狭窄用途。

总之,长话短说,有没有办法在运行时询问 Ruby 方法是在哪里定义的?像whereami( :foo ) 这样返回/path/to/some/file.rb line #45 的东西?在这种情况下,告诉我它是在 String 类中定义的将是没有帮助的,因为它被某个库重载了。

我不能保证源代码存在于我的项目中,所以寻找 'def foo' 不一定能满足我的需求,更不用说我是否有很多 def foo 的,有时直到运行时我才知道我可能会使用哪一个。

【问题讨论】:

  • 在 Ruby 1.8.7 中,专门添加了一种特殊方法来查找此信息(并且在 1.9.3 中仍然存在)...在下面我的答案中详细说明。

标签: ruby-on-rails ruby runtime methods definition


【解决方案1】:

这确实太晚了,但您可以通过以下方式找到定义方法的位置:

http://gist.github.com/76951

# How to find out where a method comes from.
# Learned this from Dave Thomas while teaching Advanced Ruby Studio
# Makes the case for separating method definitions into
# modules, especially when enhancing built-in classes.
module Perpetrator
  def crime
  end
end

class Fixnum
  include Perpetrator
end

p 2.method(:crime) # The "2" here is an instance of Fixnum.
#<Method: Fixnum(Perpetrator)#crime>

如果你使用的是 Ruby 1.9+,你可以使用source_location

require 'csv'

p CSV.new('string').method(:flock)
# => #<Method: CSV#flock>

CSV.new('string').method(:flock).source_location
# => ["/path/to/ruby/1.9.2-p290/lib/ruby/1.9.1/forwardable.rb", 180]

请注意,这不适用于所有内容,例如本机编译代码。 Method class 也有一些简洁的函数,比如 Method#owner 它返回定义方法的文件。

编辑:另请参阅 __file____line__ 以及其他答案中的 REE 注释,它们也很方便。 --wg

【讨论】:

  • source_location 似乎适用于使用 activesupport-2.3.14 的 1.8.7-p334
  • 找到方法后,试试Method的owner方法
  • 2.method(:crime) 中的第二个是什么?
  • Fixnum的实例
  • 重要提示:这不会从method_missing 中提取任何动态定义的方法。因此,如果您的模块或祖先类在method_missing 内包含class_evaldefine_method,则此方法将不起作用。
【解决方案2】:

您实际上可以比上述解决方案走得更远。对于 Ruby 1.8 企业版,Method 实例上有 __file____line__ 方法:

require 'rubygems'
require 'activesupport'

m = 2.days.method(:ago)
# => #<Method: Fixnum(ActiveSupport::CoreExtensions::Numeric::Time)#ago>

m.__file__
# => "/Users/james/.rvm/gems/ree-1.8.7-2010.01/gems/activesupport-2.3.8/lib/active_support/core_ext/numeric/time.rb"
m.__line__
# => 64

对于 Ruby 1.9 及更高版本,有 source_location(感谢 Jonathan!):

require 'active_support/all'
m = 2.days.method(:ago)
# => #<Method: Fixnum(Numeric)#ago>    # comes from the Numeric module

m.source_location   # show file and line
# => ["/var/lib/gems/1.9.1/gems/activesupport-3.0.6/.../numeric/time.rb", 63]

【讨论】:

  • 我在任何Method 类实例上都得到__file____line__ 的“NoMethodError: undefined method”,例如:method(:method).__file__
  • 你有哪个版本的 ruby​​?
  • ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
  • 在 Ruby 1.9 上,m.__file__m.__line__ 已替换为 m.source_location
  • source_location 适用于 Ruby 1.9 及更高版本,包括 2.1
【解决方案3】:

我来晚了,很惊讶没有人提到Method#owner

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A

【讨论】:

  • 我很惊讶您是第一个明确引用Method class 的人。 1.9 中引入的另一个鲜为人知的宝藏:Method#parameters
【解决方案4】:

从较新的similar question 复制我的答案,为这个问题添加了新信息。

Ruby 1.9 有一个名为source_location 的方法:

返回包含此方法的 Ruby 源文件名和行号,如果此方法未在 Ruby 中定义(即本机),则返回 nil

这已被此 gem 向后移植到 1.8.7

所以你可以请求方法:

m = Foo::Bar.method(:create)

然后求那个方法的source_location

m.source_location

这将返回一个包含文件名和行号的数组。 例如 ActiveRecord::Base#validates 这返回:

ActiveRecord::Base.method(:validates).source_location
# => ["/Users/laas/.rvm/gems/ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

对于类和模块,Ruby 不提供内置支持,但有一个出色的 Gist 以 source_location 为基础,如果没有指定方法,则返回给定方法的文件或类的第一个文件:

在行动:

where_is(ActiveRecord::Base, :validates)

# => ["/Users/laas/.rvm/gems/ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

在安装了 TextMate 的 Mac 上,这也会在指定位置弹出编辑器。

【讨论】:

    【解决方案5】:

    这可能会有所帮助,但您必须自己编写代码。从博客粘贴:

    Ruby 提供了一个 method_added() 每次调用的回调 方法被添加或重新定义在 班级。它是 Module 类的一部分, 每个类都是一个模块。有 还调用了两个相关的回调 method_removed() 和 method_undefined().

    http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby

    【讨论】:

      【解决方案6】:

      如果你可以使方法崩溃,你会得到一个回溯,它会告诉你它在哪里。

      不幸的是,如果你不能让它崩溃,那么你就无法找出它是在哪里定义的。如果您尝试通过覆盖或覆盖方法来使用该方法,那么任何崩溃都将来自您覆盖或覆盖的方法,并且没有任何用处。

      崩溃方法的有用方法:

      1. 在禁止的地方传递 nil - 很多时候该方法会在 nil 类上引发 ArgumentError 或永远存在的 NoMethodError
      2. 如果您了解该方法的内部知识,并且知道该方法又会调用其他方法,那么您可以覆盖其他方法,并在其中引发。

      【讨论】:

      • 如果您有权访问代码,您可以轻松地将require 'ruby-debug'; debugger 插入您想要插入的代码中。
      • “不幸的是,如果你不能让它崩溃,那么你就无法找到它的定义位置。”不是真的。寻找其他答案。
      【解决方案7】:

      也许#source_location 可以帮助找到方法来自哪里。

      例如:

      ModelName.method(:has_one).source_location
      

      返回

      [project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/associations.rb", line_number_of_where_method_is]
      

      ModelName.new.method(:valid?).source_location
      

      返回

      [project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
      

      【讨论】:

        【解决方案8】:

        很晚的答案:)但早期的答案对我没有帮助

        set_trace_func proc{ |event, file, line, id, binding, classname|
          printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
        }
        # call your method
        set_trace_func nil
        

        【讨论】:

        • 你为什么要通过nil
        • @ArupRakshit 来自文档:«建立 proc 作为跟踪处理程序,如果参数为 nil,则禁用跟踪。»
        【解决方案9】:

        你也许可以这样做:

        foo_finder.rb:

         class String
           def String.method_added(name)
             if (name==:foo)
                puts "defining #{name} in:\n\t"
                puts caller.join("\n\t")
             end
           end
         end
        

        然后确保 foo_finder 首先加载类似

        ruby -r foo_finder.rb railsapp
        

        (我只是弄乱了rails,所以我不知道确切,但我想有一种方法可以像这样开始它。)

        这将向您展示 String#foo 的所有重新定义。通过一点元编程,您可以将其概括为您想要的任何功能。但它确实需要在实际重新定义的文件之前加载。

        【讨论】:

          【解决方案10】:

          您始终可以使用caller() 追溯您所在的位置。

          【讨论】:

          • 这对于查找调用您的内容很有用,但在您尝试查找定义某些内容的位置时并不好。
          猜你喜欢
          • 2011-06-25
          • 1970-01-01
          • 1970-01-01
          • 2014-06-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多