2016-01-12 29 views
9

Data una classe,Come posso verificare qual è il valore predefinito per parametro opzionale nel metodo di ruby?

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

E 'possibile ottenere il valore di default per arg2, tramite alcuni metodi come Class#instance_method, o qualcosa del genere?

+1

Per curiosità, qual è il caso d'uso del perché ne hai bisogno? – jeffdill2

+1

La conversazione in questa domanda ha diverse opzioni che puoi provare: http://stackoverflow.com/questions/622324/getting-argument-names-in-ruby-reflection –

+0

Eccone un'altra: http://stackoverflow.com/questions/2452077/is-there-a-way-to-return-a-method-parameter-names-in-ruby –

risposta

4

Penso che il motivo per cui tale utilità non è disponibile è che i valori degli argomenti predefiniti vengono valutati quando devono essere assegnati. Pertanto, provare a valutarli potrebbe avere effetti collaterali aggiuntivi.


Lascia che ti racconti una storia su piani nucleari del governo russo:

Qualche tempo fa, hanno assunto ultra hard hacker russi a venire con aa soluzione che sia a prova di errore e mega sicuro che consente di lanciare tutte le armi nucleari disponibili o semplicemente eseguire una simulazione. Hanno deciso di creare un metodo chiamato launch_all_nukes, che accetta facoltativamente un argomento di parola chiave simulation_number:. Caricarono l'implementazione in un REPL e cancellarono il codice in modo che le spie nemiche non potessero mai scoprire come effettivamente funziona.


ogni giorno per gli ultimi due anni, lo specialista di fiducia Ivan si reca in una località segreta giga dove si siede di fronte a quello che sembra essere un normale IRB e valuta le possibilità di russo La Federazione sopravvive a una supposta distruzione reciproca assicurata.

$: launch_all_nukes simulation_number: 1 

...
Solo un altro giorno normale.

$: launch_all_nukes simulation_number: 2 

...

$: launch_all_nukes simulation_number: 3 

...
Anche se questi prendono 25 minuti in media, ci si sente come a volte ore.

$: launch_all_nukes simulation_number: 4 

...
a fissare lo schermo. Solo un altro giorno normale. Un altro ... regolare ... giorno ...

$: launch_all_nukes simulation_number: 5 

...
Tik-Tok, tik-tok, tik-tok ... chiedendo cosa potrebbe esserci per il pranzo?

$: launch_all_nukes simulation_number: 6 

...
Finalmente! 7 è sempre il più interessante. È l'unico che a volte mostra che c'è una probabilità dello 0.03% - 0.08% di non annientamento completo. Ivan non ha idea di cosa si nasconda dietro il numero 7. O di nessuna delle altre simulazioni. Esegue solo i comandi e aspetta. Ma sicuramente, il numero 7 è quello che porta piccoli raggi di gioia ed eccitazione nel suo incarico altrimenti noioso. Aaaaaaand, vai!

$: launch_all_nukes simulation_number: 7 

...
0%. Come tutti gli altri. Quanto regolare.

$: launch_all_nukes simulation_number: 8 

...
importanza ha in realtà? Perché una nazione dovrebbe essere superiore a tutte le altre? La vita umana è preziosa da sola, per cominciare? La Terra nel suo insieme è intrinsecamente preziosa? Solo un piccolo spettacolo di roccia che galleggia in un universo infinito ...

$: launch_all_nukes simulation_number: 9 

...
Che cosa è successo? Ivan era un grande sviluppatore. E ora ha appena fissa una console, l'esecuzione di comandi ripetitivi di volta in volta ... E 'questo ciò che il progresso si sente come ...

$: launch_all_nukes simulation_number: 10 

...
Aspetta un secondo ... Qual è il valore predefinito di simulation_number:? Che cos'è? Sicuramente, l'implementazione ha qualche controllo come __actually_launch_nukes__ if simulation_number.nil?. Ma è davvero nil? O è qualcos'altro? ...

$: launch_all_nukes simulation_number: 11 

...
Come un earworm ripetitivo, questa piccola domanda mai lasciato la sua mente ... cosa si tratta? ... Non ha mai temuto di mettere in pericolo accidentalmente il mondo perché ha visto che eseguire launch_all_nukes senza argomenti richiede tre diverse chiavi di accesso, nessuna delle quali sa.

$: launch_all_nukes simulation_number: 12 

...
Ivan ha eseguito i comandi ordinaria Ruby nella console prima. Con tutti i mezzi, è solo un irb regolare ... Basta eseguire un semplice metodo di introspezione ... Sa che non gli è permesso di farlo ... Ma nessuno lo saprà, giusto? Nessuno sa ancora come questo programma funziona in ogni caso ... Ah ...

$: launch_all_nukes simulation_number: 13 

...
13 e 14 sono i peggiori! 13 di solito dura un'ora e mezza. 14 è ancora più lungo. Dannazione, Ivan brama, solo un'infinita minuscola informazione per tenere impegnata la sua mente per almeno un paio di minuti ... Fallo!

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

...
Mortificato, Ivan ha congelato immobile come la realizzazione improvvisa lo colpì. Ora sa qual è il valore predefinito. Ma è troppo tardi...


Ecco il tentativo di un uomo povero:

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" 

Ovviamente molto soggetto ad errori:

  • Non funziona con metodi nativi
  • non funziona con i metodi definito in REPL
  • È necessario il privilegio di lettura es
  • La regex non gestisce molti casi (come i valori predefiniti più complessi che hanno spazi bianchi, ) o , in essi), ma questo può essere migliorato.

Se qualcuno esce con una soluzione puramente introspettiva, andare con quella.

4

Sembra che l'unico modo in cui possiamo esaminare i valori degli argomenti del metodo consiste nell'avere accesso al metodo binding. Utilizzando la classe Tracepoint, possiamo ottenere un oggetto vincolante e quindi controllare i valori di tutti i parametri optional.

Dobbiamo assicurarci di invocare il metodo desiderato con solo i parametri richiesti, in modo che ai parametri predefiniti vengano assegnati i loro valori predefiniti.

Di seguito è riportato il mio tentativo di farlo: funziona con entrambi i metodi di istanza e i metodi di classe. Per invocare i metodi di istanza, è necessario creare un'istanza della classe, se il costruttore richiede parametri, quindi può diventare complicato creare un oggetto. Per aggirare questo problema, questo codice crea dinamicamente una sottoclasse della classe data e definisce per essa un costruttore no-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"}