Как получить имена аргументов, используя отражение
Я хотел бы сделать довольно серьезное отражение в Ruby. Я хочу создать функцию, которая возвращает имена аргументов различных вызывающих функций выше стека вызовов (достаточно одного уровня, но зачем останавливаться на нем?). Я мог бы использовать Kernel.caller
, перейти к файлу и проанализировать список аргументов, но это было бы уродливо и ненадежно.
Функция, которую я хотел бы, будет работать следующим образом:
module A
def method1( tuti, fruity)
foo
end
def method2(bim, bam, boom)
foo
end
def foo
print caller_args[1].join(",") #the "1" mean one step up the call stack
end
end
A.method1
#prints "tuti,fruity"
A.method2
#prints "bim, bam, boom"
Я бы не прочь использовать ParseTree или какой-то подобный инструмент для этой задачи, но, глядя на Parsetree, неясно, как использовать это для этой цели. Создание расширения C, например это, является другой возможностью, но было бы неплохо, если бы кто-то уже сделал это для меня.
Я вижу, что мне, вероятно, понадобится какое-то расширение C. Полагаю, это означает, что мой вопрос заключается в том, какая комбинация расширения C будет работать наиболее легко. Я не думаю, что Caller + ParseTree будет достаточно.
Насколько я хотел бы это сделать, вместо того, чтобы говорить "автоматическая отладка", возможно, я должен сказать, что я хотел бы использовать эту функцию для автоматической проверки условий вызова и возврата функций:
def add x, y
check_positive
return x + y
end
Где check_positive
генерирует исключение, если x
и y
не были положительными. Очевидно, что было бы больше, чем это, но, надеюсь, это дает достаточную мотивацию.
Ответы
Ответ 1
Я предлагаю вам взглянуть на библиотеку Merb action-args.
require 'rubygems'
require 'merb'
include GetArgs
def foo(bar, zed=42)
end
method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]]
Если вы не хотите зависеть от Merb, вы можете выбрать и выбрать лучшие части из исходного кода в github.
Ответ 2
В Ruby 1.9.2 вы можете тривиально получить список параметров любого Proc
(и, следовательно, конечно же любого из Method
или UnboundMethod
) с помощью Proc#parameters
:
A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]]
Формат представляет собой массив пар символов: type (обязательный, необязательный, остаток, блок) и имя.
В нужном формате попробуйте
A.instance_method(:method1).parameters.map(&:last).map(&:to_s)
# => ['tuti', 'fruity']
Конечно, это все равно не дает вам доступа к вызывающему.
Ответ 3
У меня есть метод, который довольно дорог и почти работает.
$shadow_stack = []
set_trace_func( lambda {
|event, file, line, id, binding, classname|
if event == "call"
$shadow_stack.push( eval("local_variables", binding) )
elsif event == "return"
$shadow_stack.pop
end
} )
def method1( tuti, fruity )
foo
end
def method2(bim, bam, boom)
foo
x = 10
y = 3
end
def foo
puts $shadow_stack[-2].join(", ")
end
method1(1,2)
method2(3,4,4)
Выходы
tuti, fruity
bim, bam, boom, x, y
Мне любопытно, почему вы хотели бы такую функциональность таким обобщенным образом.
Мне любопытно, как вы думаете, что эта функция позволит автоматическую отладку? Вам все равно придется вводить вызовы в вашу функцию "foo". Фактически, что-то, основанное на set_trace_func
, более автоматическое, так как вам не нужно трогать существующий код. В самом деле, как debug.rb реализуется с точки зрения set_trace_func.
Решения вашего точного вопроса действительно в основном, как вы описали. использовать caller + parsetree или открыть файл и захватить данные таким образом. Нет возможности отражения, о которой я знаю, это позволит вам получить имена аргументов. Вы можете одобрить мое решение, захватив связанный объект метода и вызвав #arity
, чтобы затем определить, что из local_variables
являются аргументами, но, хотя он появляется, результат этой функции упорядочен, я не уверен, что безопасно полагаться на том. Если вы не возражаете, я спрашиваю, как только у вас будут данные и интерфейс, которые вы описываете, что вы собираетесь с ним делать? Автоматическая отладка не была тем, что первоначально приходило в голову, когда я предполагал использовать эту функциональность, хотя, возможно, с моей стороны не получается вообразить.
Ага!
Тогда я поступил бы иначе. Есть несколько рубиновых библиотек для разработки дизайна по контракту, включая ruby-contract, rdbc и т.д.
Другой вариант - написать что-то вроде:
def positive
lambda { |x| x >= 0 }
end
def any
lambda { |x| true }
end
class Module
def define_checked_method(name, *checkers, &body)
define_method(name) do |*args|
unless checkers.zip(args).all? { |check, arg| check[arg] }
raise "bad argument"
end
body.call(*args)
end
end
end
class A
define_checked_method(:add, positive, any) do |x, y|
x + y
end
end
a = A.new
p a.add(3, 2)
p a.add(3, -1)
p a.add(-4, 2)
Выходы
5
2
checked_rb.rb:13:in `add': bad argument (RuntimeError)
from checked_rb.rb:29
Конечно, это можно сделать гораздо более сложным, и действительно, некоторые из тех, что были упомянуты в библиотеках, но, возможно, это способ заставить вас туда, куда вы хотите пойти, не обязательно используя тот путь, который планируете использовать для получения есть?
Ответ 4
если вы хотите значение для значений по умолчанию, тоже есть "аргументы" gem
$ gem install rdp-arguments
$ irb
>> require 'arguments'
>> require 'test.rb' # class A is defined here
>> Arguments.names(A, :go)
Ответ 5
Фактически, описанный вами метод явно не позволяет отличить аргументы от локальных переменных, а также не работает автоматически
Это потому, что то, что вы пытаетесь сделать, - это не то, что поддерживается. Это возможно (все возможно в рубине), но нет документального или известного способа сделать это.
Либо вы можете оценить обратную трассировку, как предложенный логаном, либо вы можете выкинуть свой компилятор C и взломать исходный код для ruby. Я уверен, что нет других способов сделать это.