В Ruby, как написать метод для отображения имен переменных экземпляра любого объекта и его значений - PullRequest
2 голосов
/ 17 июня 2010

Учитывая любой объект в Ruby (на Rails), как я могу написать метод, чтобы он отображал имена переменных экземпляра этого объекта и их значения, например:

@x: 1
@y: 2
@link_to_point: #<Point:0x10031b298 @y=20, @x=38>

( Обновление: inspect сделает, за исключением большого объекта, трудно разбить переменные из 200 строк вывода, как в Rails, когда вы request.inspect или self.inspect в объект ActionView)

Я также хочу иметь возможность печатать <br> до конца значения каждой переменной экземпляра, чтобы красиво распечатать их на веб-странице.

теперь сложность заключается в том, что не каждая переменная экземпляра имеет метод доступа, поэтому ее нельзя вызвать с помощью obj.send (var_name)

(у var_name удалено «@», поэтому «@x» становится «x»)

Обновление: Полагаю, с помощью рекурсии можно распечатать более продвинутую версию:

#<Point:0x10031b462>
    @x: 1
    @y: 2
    @link_to_point: #<Point:0x10031b298>
        @x=38
        @y=20

Ответы [ 4 ]

6 голосов
/ 17 июня 2010

Я, вероятно, написал бы это так:

class Object
  def all_variables(root=true)
    vars = {}
    self.instance_variables.each do |var|
      ivar = self.instance_variable_get(var)
      vars[var] = [ivar, ivar.all_variables(false)]
    end
    root ? [self, vars] : vars
  end
end

def string_variables(vars, lb="\n", indent="\t", current_indent="")
  out             = "#{vars[0].inspect}#{lb}"
  current_indent += indent
  out            += vars[1].map do |var, ivar|
                      ivstr = string_variables(ivar, lb, indent, current_indent)
                      "#{current_indent}#{var}: #{ivstr}"
                    end.join
  return out
end

def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
  string_variables(obj.all_variables, lb, indent, current_indent)
end

Метод Object#all_variables создает массив, содержащий (0) заданный объект и (1) имена переменных экземпляра хеш-сопоставления, в массивы, содержащие (0) переменная экземпляра и (1) отображение хеша….Таким образом, это дает вам хорошую рекурсивную структуру.Функция string_variables хорошо распечатывает этот хеш;inspect_variables - это просто удобная обертка.Таким образом, print inspect_variables(foo) дает вам опцию, разделенную символом новой строки, а print inspect_variables(foo, "<br />\n") дает вам версию с переносом строки HTML.Если вы хотите указать отступ, вы также можете сделать это: print inspect_variables(foo, "\n", "|---") создает (бесполезный) формат искусственного дерева вместо отступа на основе табуляции.

Должен быть разумный способнаписать функцию each_variable, для которой вы предоставляете обратный вызов (который не должен был бы выделять промежуточное хранилище);Я отредактирую этот ответ, чтобы включить его, если я о чем-то подумаю. Редактировать 1: Я кое о чем подумал.

Вот еще один способ написать его, который я считаю слегкаприятнее:

class Object
  def each_variable(name=nil, depth=0, parent=nil, &block)
    yield name, self, depth, parent
    self.instance_variables.each do |var|
      self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
    end
  end
end

def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
  out = ''
  obj.each_variable do |name, var, depth, _parent|
    out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
  end
  return out
end

Метод Object#each_variable принимает ряд необязательных аргументов, которые не предназначены для указания пользователем;вместо этого они используются рекурсией для поддержания состояния.Указанному блоку передаются (а) имя переменной экземпляра или nil, если переменная является корнем рекурсии;(б) переменная;(c) глубина, до которой спускалась рекурсия;и (d) родительский элемент текущей переменной или nil, если указанная переменная является корнем рекурсии.Рекурсия в глубину.Функция inspect_variables использует это для создания строки.Аргумент obj - это объект для итерации;nl - разделитель строк;indent - отступ, применяемый на каждом уровне;и sep разделяет имя и значение.

Редактировать 2: Это на самом деле ничего не добавляет к ответу на ваш вопрос, но: просто чтобы доказать, что у нас нетпотерял что-то в переопределении, вот переопределение all_variables в терминах each_variables.

def all_variables(obj)
  cur_depth = 0
  root      = [obj, {}]

  tree      = root
  parents   = []
  prev      = root

  obj.each_variable do |name, var, depth, _parent|
    next unless name

    case depth <=> cur_depth
      when -1 # We've gone back up
        tree = parents.pop(cur_depth - depth)[0]
      when +1 # We've gone down
        parents << tree
        tree = prev
      else # We're at the same level
        # Do nothing
    end

    cur_depth            = depth
    prev = tree[1][name] = [var, {}]
  end

  return root
end

Я чувствую, что это должно быть короче, но это может быть невозможно;поскольку у нас сейчас нет рекурсии, мы должны явно поддерживать стек (в parents).Но это возможно, поэтому метод each_variable работает так же хорошо (и я думаю, что он немного лучше).

5 голосов
/ 17 июня 2010

Понятно ... Antal, должно быть, дает здесь расширенную версию ...

короткая версия, вероятно, будет:

def p_each(obj)
  obj.instance_variables.each do |v|
    puts "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  nil
end

или чтобы вернуть ее в виде строки:

def sp_each(obj)
  s = ""
  obj.instance_variables.each do |v|
    s += "#{v}: #{obj.instance_variable_get(v)}\n"
  end
  s
end

или короче:

def sp_each(obj)
  obj.instance_variables.map {|v| "#{v}: #{obj.instance_variable_get(v)}\n"}.join
end
1 голос
/ 17 июня 2010

Это быстрая адаптация простого эмиттера JSON, который я написал для другого вопроса :

class Object
  def inspect!(indent=0)
    return inspect if instance_variables.empty?
    "#<#{self.class}:0x#{object_id.to_s(16)}\n#{'  ' * indent+=1}#{
      instance_variables.map {|var|
        "#{var}: #{instance_variable_get(var).inspect!(indent)}"
      }.join("\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}>"
  end
end

class Array
  def inspect!(indent=0)
    return '[]' if empty?
    "[\n#{'  ' * indent+=1}#{
      map {|el| el.inspect!(indent) }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}]"
  end
end

class Hash
  def inspect!(indent=0)
    return '{}' if empty?
    "{\n#{'  ' * indent+=1}#{
      map {|k, v|
        "#{k.inspect!(indent)} => #{v.inspect!(indent)}"
      }.join(",\n#{'  ' * indent}")
    }\n#{'  ' * indent-=1}}"
  end
end

Это все волшебство, правда. Теперь нам нужны только некоторые простые значения по умолчанию для некоторых типов, когда полная проверка не имеет смысла (nil, false, true, числа и т. Д.):

module InspectBang
  def inspect!(indent=0)
    inspect
  end
end

[Numeric, Symbol, NilClass, TrueClass, FalseClass, String].each do |klass|
  klass.send :include, InspectBang
end
0 голосов
/ 17 июня 2010

Как это?

# Get the instance variables of an object
d = Date.new
d.instance_variables.each{|i| puts i + "<br />"}

Документация Ruby для instance_variables .

Эта концепция обычно называется "самоанализом" (для изучения самого себя).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...