【问题标题】:How to get a detailed list of methods defined in a Ruby class via the command line?如何通过命令行获取 Ruby 类中定义的方法的详细列表?
【发布时间】:2021-12-27 20:33:30
【问题描述】:

我正在寻找一种方法来生成 Ruby 类中所有方法的列表,类似于 C 头文件。我想要这个,这样我就可以看到类的概述,而无需使用 IDE 代码折叠来折叠所有内容。

最好这是一个 *nix 命令行函数,以便我可以使用其他命令行工具进一步处理它。

输出类似于

def foo( a, b, c )
def bar
def baz( d )

类似于this question,除了我正在寻找一个包含参数的列表并且我想输出到命令行以便进一步处理它。目标是在我工作时让这个列表可见,所以我希望它一目了然。

【问题讨论】:

  • 您可以使用 Module#instance_methods 和参数 false。例如,Enumerator.instance_methods(false) #=>[:each_with_object, :+, :inspect, :rewind, :next, :with_index, :with_object, :next_values, :peek_values, :peek, :feed, :size, :each, :each_with_index]。见Enumerator
  • @Cary Swoveland 这很有帮助,因为它只显示用户定义的方法,但我特别想要包含函数参数的输出。
  • 是的,然后将每个符号转换为带有Object#method的方法,然后在该方法上调用Method#parameters
  • 这能回答你的问题吗? How to list all methods for an object in Ruby?

标签: ruby function class command-line


【解决方案1】:

对该类使用instance_methods,然后查询参数

例如

class A 
  def foo
  end
  def bar(a,b,c)
  end
end


A.instance_methods(false).each do |s|
   print "def #{s}(#{A.instance_method(s).parameters})\n"
end

输出:

def foo([])
def bar([[:req, :a], [:req, :b], [:req, :c]])

您可能需要获取参数数组的子进程才能仅获取名称。

至于命令行,只需将其保存到 ruby​​ 脚本中即可。

【讨论】:

  • 你去......更好,会适应我的答案
  • 卡里是对的。你的两个答案的组合创造了一个很好的起点。我还发现 @OscarRyz 的答案需要将 a.method(s) 更改为 a.instance_method(s) 否则我会收到 NameError。
  • @coffee-dan 好吧,instance_method 是一个类方法,不是实例方法,但是是的。 @CarySwoveland 太晚了:)
  • @CarySwoveland 哦,伙计,现在我感觉很糟糕。这主要是你的答案
【解决方案2】:

如果您想获取Module 中定义的所有方法,您可以使用Module#instance_methods 系列方法之一,具体取决于确切您要查找的内容: p>

每一个都有一个可选的布尔参数include_super=true,它可以让你决定是包括继承的方法(默认)还是只从你将消息发送到的确切模块中返回方法(当传递false时)。

如果你想获取这些方法的参数,你首先需要获取一个UnboundMethod反射代理对象,它代表你感兴趣的方法。你可以使用Module#instance_method来做到这一点。

一旦有了UnboundMethod,就可以使用UnboundMethod#parameters 来获取方法参数的描述。但是请注意,您确实不会获得可选参数的默认参数。这实际上是不可能的。

使用这些构建块,您可以构建如下内容:

class MethodHeaderFormatter
  private

  attr_accessor :name, :parameter_list

  def initialize(name, parameter_list)
    self.name = name
    self.parameter_list = MethodParameterListFormatter.new(parameter_list)
  end

  public

  def to_s = "def #{name}" + if parameter_list.empty? then '' else "(#{parameter_list})" end

  class MethodParameterListFormatter
    private

    attr_accessor :parameter_list

    def initialize(parameter_list)
      self.parameter_list = parameter_list.map(&MethodParameterFormatter.method(:[]))
    end

    public

    def empty? = parameter_list.empty?

    def to_s = parameter_list.join(', ')

    module MethodParameterFormatter
      private

      attr_accessor :name, :prefix, :suffix

      def initialize(name) = self.name = name

      public

      def self.[]((type, name)) = const_get(:"#{type.capitalize}MethodParameterFormatter").new(name)

      def to_s = "#{prefix}#{name}#{suffix}"

      class ReqMethodParameterFormatter; include MethodParameterFormatter end

      class OptMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = '=unknown'
        end
      end

      class RestMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '*'
        end
      end

      class KeyreqMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = ':'
        end
      end

      class KeyMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.suffix = ': unknown'
        end
      end

      class KeyrestMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '**'
        end
      end

      class BlockMethodParameterFormatter
        include MethodParameterFormatter

        def initialize(name)
          super
          self.prefix = '&'
        end
      end

      private_constant *constants
    end

    private_constant *constants
  end

  private_constant *constants
end

你可以这样使用它:

module Test
  def foo(a, b, c) end
  def bar; end
  def baz(d) end
  def quux(m, o = 23, *r, k:, ok: 42, **kr, &b) end
  alias_method :blarf, :quux
  attr_accessor :frotz
end

puts Test.public_instance_methods(false).map { |meth| MethodHeaderFormatter.new(meth, Test.instance_method(meth).parameters) }
# def baz(d)
# def quux(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz=()
# def blarf(m, o=unknown, *r, k:, ok: unknown, **kr, &b)
# def frotz
# def foo(a, b, c)
# def bar

但是,请注意,列出某些模块的方法并不为您提供该模块的协议(即可以理解的消息集)! p>

这里有两个简单的示例,其中模块中定义的方法集与该模块实例所理解的消息集不对应:

class Foo
  def bar = raise(NoMethodError)
  def respond_to?(meth) = meth != :bar && super
end

foo = Foo.new
foo.respond_to?(:bar) #=> false
foo.bar               # NoMethodError

虽然这是一个愚蠢的例子,而且代码希望没人会真正编写,但它清楚地表明虽然 Foo 有一个名为 bar 的方法,但它的实例不会像您那样响应 bar 消息会期待的。

这是一个更现实的例子:

class Bar
  def method_missing(meth, *) = if meth == :foo then 'Fooooo!' else super end
  def respond_to_missing?(meth, *) = meth == :foo || super
end

bar = Bar.new
bar.respond_to?(:foo) #=> true
bar.foo               #=> 'Fooooo!'

最后,如果你希望你能找到一些疯狂的元编程抽象解释技巧,实际上可以让你列出模块的整个协议,让我打消你的想法:

class Quux
  def method_missing(*); end
  def respond_to_missing?(*) = true
end

Voilà:一个类,其实例响应无限数量的消息,实际上,它们响应每条可能的消息。如果您认为这不切实际,那么实际上,Ruby 世界中使用最广泛的库之一就是这样:ActiveRecord。

【讨论】:

    【解决方案3】:

    使用 TypeProf 创建 RBS 文件

    对于 Ruby >= 2.7.1,正确的答案通常是使用 RBSTypeProf 来创建(非常粗略的)等效的头文件。由于此时您发布的代码甚至还不是一个类,并且不包含任何可推断的类型信息,因此您的大多数类型都可能是“无类型的”,并且由您来填写类型。

    类型 检查 不是由 Ruby 原生处理的。为此,您需要使用 Steep、Sorbet 或类似的东西。也就是说,出于文档目的,如果您还没有良好的 YARD 文档,TypeProf 可能是您最好的选择,而 RBS 原型作为一个合理的后备方案。例如,给定以下 Ruby 源文件:

    class Example
      def foo(a, b, c); end
      def bar; end
      def baz(d); end
    end
    

    运行 typeprof example.rb 会产生:

    # TypeProf 0.20.2
    
    # Classes
    class Example
      def foo: (untyped a, untyped b, untyped c) -> nil
      def bar: -> nil
      def baz: (untyped d) -> nil
    end
    

    在可以构建、解析 AST 并通过 TypeProf 运行代码路径的真实代码库上,它在推断常见类型方面做得相当合理,尽管有一些例外,并且它不适用于某些元编程结构体。尽管如此,它在大多数情况下都能满足您的要求。

    老实说,除非您打算进行类型检查,否则从文档的角度来看,对@param@return 使用YARD 标签通常会产生更有用的结果。基于文档的打字的问题是必须积极维护文档。否则,文档可能会基于程序员的错误或疏忽而存在。这就是 RBS 和 TypeProf 的优势所在:它们基于实际代码,而不是程序员编辑到文件中的 cmets。因此,您的里程将根据您的用例而有所不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多