2016-01-12 15 views
9

eine Klasse gegeben,Wie kann ich überprüfen, was der Standardwert für optionale Parameter in Rubys Methode ist?

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

Ist es möglich, den Standardwert für arg2, über einige Methoden wie Class#instance_method, oder etwas zu bekommen?

+1

Aus Neugier, was ist der Anwendungsfall dafür, warum Sie das brauchen? – jeffdill2

+1

Konversation in dieser Frage hat mehrere Optionen, die Sie ausprobieren können: http://stackoverflow.com/questions/622324/getting-argument-names-in-ruby-reflection –

+0

Hier ist eine andere: http://stackoverflow.com/questions/2452077/is-there-a-way-to-return-a-method-parameter-names-in-ruby –

Antwort

4

Ich denke, der Grund, warum ein solches Dienstprogramm nicht verfügbar gemacht wird, besteht darin, dass die Werte der Standardargumente ausgewertet werden, wenn sie zugewiesen werden müssen. Daher könnte der Versuch, sie zu bewerten, zusätzliche Nebenwirkungen haben.


Lassen Sie mich Ihnen eine Geschichte erzählen über die Atompläne der russischen Regierung:

Vor einiger Zeit stellten sie extrem hardcore russische Hacker mit AA-Lösung zu entwickeln, die sowohl die fehlersicher und mega ist Sicher, das erlaubt entweder alle verfügbaren Atomwaffen zu starten oder einfach eine Simulation zu starten. Sie beschlossen, eine Methode mit dem Namen launch_all_nukes zu erstellen, die optional ein Schlüsselwortargument simulation_number: akzeptiert. Sie luden die Implementierung in eine REPL und löschten den Code, damit feindliche Spione niemals herausfinden konnten, wie es tatsächlich funktioniert.


Jeden Tag für den letzten paar Jahren der vertrauenswürdige Spezialist Ivan reist zu einem Giga geheimen Ort, wo er vor sitzt, was ein regulärer irb sein sieht und wertet die Chancen des russischen Die Föderation überlebt eine angebliche gegenseitige sichere Zerstörung.

$: launch_all_nukes simulation_number: 1 

...
Nur ein weiterer normaler Tag. manchmal

$: launch_all_nukes simulation_number: 2 

...

$: launch_all_nukes simulation_number: 3 

...
Auch wenn diese 25 Minuten im Durchschnitt nehmen, fühlt es sich wie Stunden.

$: launch_all_nukes simulation_number: 4 

...
starrte auf den Bildschirm. Nur ein normaler Tag. Ein weiterer ... regelmäßiger ... Tag ...

$: launch_all_nukes simulation_number: 5 

...
Tik-tok, Tick-Tack, Tick-Tack ... fragt sich, was könnte es zum Mittagessen?

$: launch_all_nukes simulation_number: 6 

...
Endlich! 7 ist immer am interessantesten. Es ist die einzige, die manchmal zeigt, dass es eine Wahrscheinlichkeit von 0,03% - 0,08% für eine nicht vollständige Vernichtung gibt. Ivan hat keine Ahnung, was hinter der Nummer 7 steht. Oder irgendeine der anderen Simulationen für diese Angelegenheit. Er führt nur Befehle aus und wartet. Aber sicherlich ist Nummer 7 die eine, die kleine Strahlen der Freude und Aufregung in seiner ansonsten langweiligen Aufgabe bringt. Aaaaaaand, geh!

$: launch_all_nukes simulation_number: 7 

...
0%. Wie alle anderen auch. Wie regelmäßig.

$: launch_all_nukes simulation_number: 8 

...
Spielt es eine Rolle, eigentlich? Warum sollte eine Nation allen anderen überlegen sein? Ist das menschliche Leben von Anfang an wertvoll? Ist die Erde insgesamt von Natur aus wertvoll? Nur ein winziges Schauspiel des Felsens in einem endlosen Universum floating ...

$: launch_all_nukes simulation_number: 9 

...
Was ist passiert? Ivan war ein großartiger Entwickler. Und jetzt starrt er nur an einer Konsole, läuft sich wiederholende Befehle von Zeit zu Zeit ... Ist es das, was Fortschritt fühlt sich an wie ...

$: launch_all_nukes simulation_number: 10 

...
Warte einen Moment ... Was das ist Standardwert von simulation_number:? Was ist es? Sicherlich hat die Implementierung einige Überprüfung wie __actually_launch_nukes__ if simulation_number.nil?. Aber ist es wirklich nil? Oder ist es etwas anderes? ...

$: launch_all_nukes simulation_number: 11 

...
Wie eine sich wiederholende Ohrwurm, diese kleine Frage verließ nie in den Sinn ... was ist es? ... Er befürchtete nie, die Welt versehentlich zu gefährden, weil er sah, dass das Ausführen von launch_all_nukes ohne Argumente zu drei verschiedenen Zugriffsschlüsseln auffordert, von denen er keine kennt.

$: launch_all_nukes simulation_number: 12 

...
Ivan hat lief, bevor in der Konsole gewöhnlichen Ruby-Befehle. Auf jeden Fall ist es nur ein gewöhnlicher Irb ... Ich mache nur eine einfache Selbstbeobachtungsmethode ... Er weiß, dass er es nicht tun darf ... Aber niemand wird es wissen, oder? Niemand weiß sogar, wie dieses Programm funktioniert sowieso ... Ah ...

$: launch_all_nukes simulation_number: 13 

...
13 und 14 sind die schlimmsten! 13 dauert normalerweise anderthalb Stunden. 14 ist noch länger. Verdammt, Ivan sehnt sich nach einer winzigen, winzigen Information, die ihn mindestens ein paar Minuten lang beschäftigt hält ... Laß es!

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

...
Mortified, erstarrte Ivan regungslos wie die plötzliche Erkenntnis ihn getroffen. Er weiß jetzt, wie der Standardwert ist. Aber es ist zu spät ...


Hier ist ein armer Versuch des Menschen:

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" 

Offensichtlich sehr fehleranfällig:

  • mit Funktioniert nicht nativen Methoden
  • Ist mit Methoden nicht funktionieren definiert in REPLs
  • Sie müssen lesen privileg es
  • Die Regex behandelt nicht viele Fälle (wie komplexere Standardwerte, die Whitespaces enthalten, ) oder , in ihnen), aber das kann verbessert werden.

Wenn jemand mit einer rein introspektiven Lösung kommt, damit gehen.

4

Es scheint, dass wir die Werte von Methodenargumenten nur untersuchen können, wenn wir Zugriff auf binding der Methode haben. Unter Verwendung der Tracepoint Klasse können wir ein solches bindendes Objekt erhalten und dann die Werte aller optional Parameter überprüfen.

Wir müssen sicherstellen, dass wir die gewünschte Methode nur mit den erforderlichen Parametern aufrufen, so dass die Standardparameter ihre Standardwerte erhalten.

Unten ist mein Versuch, dies zu tun - es funktioniert mit beiden Instanzmethoden und Klassenmethoden. Um Instanzenmethoden aufzurufen, müssen wir die Klasse instanziieren - wenn der Konstruktor Parameter benötigt, kann es schwierig werden, ein Objekt zu erzeugen. Um dieses Problem zu umgehen, erstellt dieser Code dynamisch eine Unterklasse der angegebenen Klasse und definiert einen Konstruktor ohne Arg für ihn.

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"}