2016-01-12 6 views
9

Biorąc pod uwagę klasę,Jak mogę sprawdzić, jaka jest domyślna wartość parametru opcjonalnego w metodzie ruby?

class MyClass 
    def index(arg1, arg2="hello") 
    end 
end 

Czy to możliwe, aby uzyskać wartość domyślną dla arg2, przez niektórych metod, takich jak Class#instance_method, czy coś?

+1

Z ciekawości, jaki jest pożytek z tego, dlaczego tego potrzebujesz? – jeffdill2

+1

Rozmowa w tym pytaniu ma kilka opcji, które możesz wypróbować: http://stackoverflow.com/questions/622324/getting-argument-names-in-ruby-reflection –

+0

Oto kolejna: http://stackoverflow.com/questions/2452077/is-there-a-way-to-return-a-method-parameter-names-in-ruby –

Odpowiedz

4

Myślę, że powodem, dla którego takie narzędzie nie jest dostępne, jest to, że wartości domyślnych argumentów są szacowane, gdy muszą zostać przypisane. Dlatego próby ich oceny mogą mieć dodatkowe efekty uboczne.


Opowiem ci historię o planach atomowych rosyjskiego rządu:

Jakiś czas temu, zatrudnili ultra Hardcore rosyjscy hakerzy wymyślić aa rozwiązanie, które jest zarówno bezbłędny i mega bezpieczny, który pozwala na uruchomienie wszystkich dostępnych broni nuklearnych lub po prostu uruchomienie symulacji. Zdecydowali się stworzyć jedną metodę o nazwie launch_all_nukes, która opcjonalnie akceptuje argument słowa kluczowego simulation_number:. Załadowano implementację w postaci REPL i usunięto kod, aby szpiedzy wroga nie mogli się dowiedzieć, jak to działa.


Każdego dnia w ciągu ostatnich kilku lat, zaufany specjalista Ivan podróżuje do giga tajnym miejscu, gdzie znajduje się z przodu, co wydaje się być zwykłym IRB i ocenia szanse na rosyjskim Federacja przetrwała rzekomą obopólną gwarancję zniszczenia.

$: launch_all_nukes simulation_number: 1 

...
Kolejny regularny dzień.

$: launch_all_nukes simulation_number: 2 

...

$: launch_all_nukes simulation_number: 3 

...
Nawet te 25 minut na średniej, czuje się jak godziny czasami.

$: launch_all_nukes simulation_number: 4 

...
wpatrując się w ekran. Kolejny zwykły dzień. Innym ... regularne ... dzień ...

$: launch_all_nukes simulation_number: 5 

...
Tik-tok, tik-tok, tik-tok ... Zastanawiasz się, co może być tam na obiad?

$: launch_all_nukes simulation_number: 6 

...
Wreszcie! 7 jest zawsze najciekawszy. Jest to jedyna, która czasami pokazuje, że istnieje 0,03% - 0,08% szans na nie całkowitą anihilację. Ivan nie ma pojęcia, co stoi za liczbą 7. Lub jakąkolwiek inną symulację. Po prostu uruchamia polecenia i czeka. Ale z pewnością numer 7 to ten, który niesie wiele radości i podekscytowania w swoim niecodziennym zadaniu. Aaaaaand, gotowe!

$: launch_all_nukes simulation_number: 7 

...
0%. Jak wszystkie inne. Jak regularne.

$: launch_all_nukes simulation_number: 8 

...
Czy to ważne, rzeczywiście? Dlaczego jeden naród byłby lepszy od wszystkich innych? Czy na początku ludzkie życie jest cenne? Czy Ziemia jako całość jest z natury wartościowa? Wystarczy mały spektakl skale pływającego w nieskończonym Wszechświecie ...

$: launch_all_nukes simulation_number: 9 

...
Co się stało? Ivan był świetnym deweloperem. A teraz po prostu patrzy na konsoli, prowadzenie powtarzalnych poleceń od czasu do czasu ... Czy to, co czuje się jak postęp ...

$: launch_all_nukes simulation_number: 10 

...
Chwileczkę ... Co to jest domyślna wartość simulation_number:? Co to jest? Z pewnością implementacja ma pewną kontrolę, taką jak __actually_launch_nukes__ if simulation_number.nil?. Ale czy to naprawdę nil? A może to coś innego? ...

$: launch_all_nukes simulation_number: 11 

...
Jak powtarzalnych słonecznicy, nigdy ten drobny pytanie opuścił swój umysł ... co to jest? ... Nie bał się przypadkowo narażać świata, ponieważ zobaczył, że uruchomienie launch_all_nukes bez argumentów powoduje wyświetlenie trzech różnych kluczy dostępu, z których żaden nie wie.

$: launch_all_nukes simulation_number: 12 

...
Ivan prowadził zwyczajne Ruby poleceń w konsoli wcześniej. Z całą pewnością jest to zwykła irb ... Po prostu uruchamia jedną prostą metodę introspekcji ... Wie, że nie wolno mu tego robić ... Ale nikt nie będzie wiedział, prawda? Nikt nawet nie wie, jak ten program działa tak ... Ach ...

$: launch_all_nukes simulation_number: 13 

...
13 i 14 są najgorsze! 13 zwykle zajmuje półtorej godziny. 14 jest jeszcze dłuższy. Niech to szlag, Iwan pragnie, tylko drobiazgowe, drobne informacje, które utrzymują jego umysł przez co najmniej kilka minut ... Zróbmy to!

$: method(:launch_all_nukes).default_value_for(:simulation_number) 

...
upokorzony, Ivan zastygł w bezruchu, gdy nagle uderzył go realizacja. Teraz wie, jaka jest wartość domyślna. Ale jest już za późno ...


Oto biedaka próba:

argument_name = 'arg2' 

origin_file, definition_line = MyClass.instance_method(:index).source_location 
method_signature = IO.readlines(origin_file)[definition_line.pred] 
eval(method_signature.match(/#{argument_name}\s*[=:]\s*\K[^\s),]*/)[0]) # => "hello" 

Oczywiście bardzo podatne na błędy:

  • Nie działa z rodzimymi metodami
  • nie działa z metod zdefiniowane w REPLs
  • Potrzebujesz uprawnienia do odczytu es
  • Wykonywanie nie obsługuje wielu przypadków (takich jak bardziej złożone wartości domyślne z białymi znakami, ) lub ,), ale można to poprawić.

Jeśli ktoś wyjdzie z czysto introspekcyjnej rozwiązania, iść z tym.

4

Wygląda na to, że jedyną metodą sprawdzenia wartości argumentów metody jest uzyskanie dostępu do metody binding. Korzystając z klasy Tracepoint, możemy zdobyć taki obiekt wiążący, a następnie sprawdzić wartości wszystkich parametrów .

Musimy upewnić się, że wywoływana jest pożądana metoda z tylko wymaganymi parametrami, aby domyślne parametry zostały przypisane do ich wartości domyślnych.

Poniżej znajduje się moja próba dokonania tego - działa zarówno z metodami instancji, jak i metodami klasy. Aby wywołać metody instancji, musimy utworzyć instancję klasy - jeśli konstruktor wymaga parametrów, może to być trudne do utworzenia obiektu. Aby obejść ten problem, ten kod dynamicznie tworzy podklasę danej klasy i definiuje dla niej konstruktor bez arg.

class MyClass 

    # one arg constructor to make life complex 
    def initialize param 
    end 

    def index(arg1, arg2="hello", arg3 = 1, arg4 = {a:1}, arg5 = [1,2,3]) 
    raise "Hi" # for testing purpose 
    end 

    def self.hi(arg6, arg7="default param") 
    end 
end 

def opt_values(clazz, meth) 
    captured_binding = nil 

    TracePoint.new(:call) do |tp| 
     captured_binding = tp.binding 
    end.enable { 
     # Dummy sub-class so that we can create instances with no-arg constructor 
     obj = Class.new(clazz) do 
      def initialize 
      end 
     end.new 

     # Check if it's a class method 
     meth_obj = clazz.method(meth) rescue nil 

     # If not, may be an instance method. 
     meth_obj = obj.method(meth) rescue nil if not meth_obj 

     if meth_obj 
      params = meth_obj.parameters 
      optional_params = params.collect {|i| i.last if i.first == :opt}.compact 
      dummy_required_params = [""] * (params.size - optional_params.size) 

      # Invoke the method, and handle any errors raise    
      meth_obj.call *dummy_required_params rescue nil 

      # Create a hash for storing optional argument name and its default value 
      optional_params.each_with_object({}) do |i, hash| 
       hash[i] = captured_binding.local_variable_get(i) 
      end 
     end 
    } 
end 

p opt_values MyClass, :index 
#=> {:arg2=>"hello", :arg3=>1, :arg4=>{:a=>1}, :arg5=>[1, 2, 3]} 
p opt_values MyClass, :hi 
#=> {:arg7=>"default param"} 
Powiązane problemy