Nehmen wir etwas sehr Einfaches,
# Foo.pm
package Foo {
my $baz = bar();
sub bar { 42 }; ## Overwrite this
print $baz; ## Before this is executed
}
Gibt es überhaupt eine Möglichkeit, test.pl
Code auszuführen, der die $baz
Einstellungen ändert und dazu führt Foo.pm
, dass etwas anderes auf dem Bildschirm gedruckt wird?
# maybe something here.
use Foo;
# maybe something here
Ist es mit den Compiler-Phasen möglich, das Drucken der oben genannten zu erzwingen 7
?
perl
compilation
Evan Carroll
quelle
quelle
Foo::bar
, aber esuse Foo
wird sowohl die Kompilierungsphase (Neudefinition des Balkens, wenn dort zuvor etwas definiert wurde) als auch die Laufzeitphase von Foo ausgeführt. Das einzige, was ich mir vorstellen kann, wäre ein zutiefst hackiger@INC
Hook, um zu ändern, wie Foo geladen wird.require
(und damituse
) kompiliert und führt das Modul aus, bevor es zurückkehrt. Gleiches gilt füreval
.eval
kann nicht zum Kompilieren von Code verwendet werden, ohne ihn auch auszuführen.Antworten:
Ein Hack ist erforderlich, da
require
(und damituse
) das Modul vor der Rückkehr kompiliert und ausgeführt wird.Gleiches gilt für
eval
.eval
kann nicht zum Kompilieren von Code verwendet werden, ohne ihn auch auszuführen.Die am wenigsten aufdringliche Lösung, die ich gefunden habe, wäre das Überschreiben
DB::postponed
. Dies wird aufgerufen, bevor eine kompilierte erforderliche Datei ausgewertet wird. Leider wird es nur beim Debuggen aufgerufen (perl -d
).Eine andere Lösung wäre, die Datei zu lesen, zu ändern und die geänderte Datei auszuwerten, ähnlich wie im Folgenden:
Das oben Gesagte ist nicht richtig eingestellt
%INC
, es bringt den Dateinamen durcheinander, der von Warnungen und dergleichen verwendet wird, es ruft nicht aufDB::postponed
usw. Das Folgende ist eine robustere Lösung:Ich habe verwendet
UNITCHECK
(was nach der Kompilierung, aber vor der Ausführung aufgerufen wird), weil ich die Überschreibung (usingunread
) vorangestellt habe, anstatt die gesamte Datei einzulesen und die neue Definition anzuhängen. Wenn Sie diesen Ansatz verwenden möchten, können Sie ein Dateihandle erhalten, mit dem Sie zurückkehren könnenEin großes Lob an @Grinnz für die Erwähnung von
@INC
Hooks.quelle
Da die einzigen Optionen hier sehr hackig sein werden, möchten wir hier wirklich Code ausführen, nachdem das Unterprogramm zum
%Foo::
Stash hinzugefügt wurde :quelle
Dies gibt einige Warnungen aus, druckt jedoch 7:
Zuerst definieren wir
Foo::bar
. Sein Wert wird durch die Deklaration in Foo.pm neu definiert, aber die Warnung "Subroutine Foo :: bar redefined" wird ausgelöst, wodurch der Signalhandler aufgerufen wird, der die Subroutine erneut definiert, um 7 zurückzugeben.quelle
perl -w
.Hier ist eine Lösung, die das Einbinden des Modulladevorgangs mit den Readonly-Making-Funktionen des Readonly-Moduls kombiniert:
quelle
Ich habe meine Lösung hier überarbeitet, so dass sie nicht mehr darauf beruht
Readonly.pm
, nachdem ich erfahren habe, dass ich eine sehr einfache Alternative verpasst habe, basierend auf der Antwort von m-conrad , die ich in den modularen Ansatz überarbeitet habe, den ich hier begonnen hatte.Foo.pm ( wie im Eröffnungsbeitrag )
OverrideSubs.pm Aktualisiert
test-run.pl
Ausführen und ausgeben:
quelle
Wenn das
sub bar
InnereFoo.pm
einen anderen Prototyp als eine vorhandeneFoo::bar
Funktion hat, wird Perl ihn nicht überschreiben? Das scheint der Fall zu sein und macht die Lösung ziemlich einfach:oder so ähnlich
Update: Nein, der Grund dafür ist, dass Perl eine "konstante" Unterroutine (mit Prototyp
()
) nicht neu definiert. Dies ist also nur dann eine praktikable Lösung, wenn Ihre Scheinfunktion konstant ist.quelle
BEGIN { *Foo::bar = sub () { 7 } }
ist besser geschrieben alssub Foo::bar() { 7 }
sub bar { 42 } my $baz = bar();
stattdessen das üblichere verwendetmy $baz = bar(); sub bar { 42 }
hätte, würde es nicht funktionieren.Prototype mismatch: sub Foo::bar () vs none at Foo.pm line 5.
undConstant subroutine bar redefined at Foo.pm line 5.
)Lass uns einen Golfwettbewerb veranstalten!
Dies stellt dem Code des Moduls lediglich eine Ersetzung der Methode voran. Dies ist die erste Codezeile, die nach der Kompilierungsphase und vor der Ausführungsphase ausgeführt wird.
Füllen Sie dann den
%INC
Eintrag aus, damit zukünftige Ladungenuse Foo
das Original nicht einlesen.quelle