Hinweis: Diese Frage dient nur zu Informationszwecken. Ich bin gespannt, wie tief es in Pythons Interna geht.
Vor nicht allzu langer Zeit begann eine Diskussion innerhalb einer bestimmten Frage, ob die an print-Anweisungen übergebenen Zeichenfolgen nach / während des Aufrufs von geändert werden könnten print
. Betrachten Sie zum Beispiel die Funktion:
def print_something():
print('This cat was scared.')
Wenn nun ausgeführt print
wird, sollte die Ausgabe an das Terminal Folgendes anzeigen:
This dog was scared.
Beachten Sie, dass das Wort "Katze" durch das Wort "Hund" ersetzt wurde. Irgendwo konnte diese internen Puffer irgendwie geändert werden, um das zu ändern, was gedruckt wurde. Angenommen, dies erfolgt ohne die ausdrückliche Erlaubnis des ursprünglichen Code-Autors (daher Hacking / Hijacking).
Insbesondere dieser Kommentar des weisen @abarnert brachte mich zum Nachdenken:
Es gibt verschiedene Möglichkeiten, dies zu tun, aber sie sind alle sehr hässlich und sollten niemals durchgeführt werden. Der am wenigsten hässliche Weg besteht darin, das
code
Objekt innerhalb der Funktion wahrscheinlich durch ein Objekt mit einer anderenco_consts
Liste zu ersetzen . Next greift wahrscheinlich in die C-API, um auf den internen Puffer des Str zuzugreifen. [...]
Es sieht also so aus, als wäre dies tatsächlich möglich.
Hier ist meine naive Herangehensweise an dieses Problem:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Natürlich exec
ist es schlecht, aber das beantwortet die Frage nicht wirklich, da es während des Aufrufs von / nach nichts ändert print
.
Wie würde es gemacht werden, wie @abarnert es erklärt hat?
42
in zu ändern ,23
als warum es eine schlechte Idee ist, den Wert von"My name is Y"
in zu ändern"My name is X"
.Antworten:
Erstens gibt es tatsächlich einen viel weniger hackigen Weg. Wir wollen nur ändern, welche
print
Drucke gedruckt werden, oder?Oder Sie können auch Monkeypatch
sys.stdout
anstelle vonprint
.Auch nichts falsch mit der
exec … getsource …
Idee. Natürlich ist daran viel auszusetzen, aber weniger als das, was hier folgt ...Wenn Sie jedoch die Codekonstanten des Funktionsobjekts ändern möchten, können wir dies tun.
Wenn Sie wirklich mit Codeobjekten herumspielen möchten, sollten Sie eine Bibliothek wie
bytecode
(wenn sie fertig ist) oderbyteplay
(bis dahin oder für ältere Python-Versionen) verwenden, anstatt sie manuell auszuführen. Selbst für etwas so Triviales ist derCodeType
Initialisierer ein Schmerz; Wenn Sie tatsächlich Dinge wie das Reparierenlnotab
erledigen müssen, würde dies nur ein Verrückter manuell tun.Es versteht sich auch von selbst, dass nicht alle Python-Implementierungen Codeobjekte im CPython-Stil verwenden. Dieser Code funktioniert in CPython 3.7 und wahrscheinlich alle Versionen auf mindestens 2.2 mit ein paar geringfügigen Änderungen (und nicht das Code-Hacking-Zeug, sondern Dinge wie Generatorausdrücke), aber er funktioniert mit keiner Version von IronPython.
Was könnte beim Hacken von Codeobjekten schief gehen? Meistens nur Segfaults,
RuntimeError
s, die den gesamten Stapel verschlingen, normalereRuntimeError
s, die gehandhabt werden können, oder Müllwerte, die wahrscheinlich nur einTypeError
oder auslösen,AttributeError
wenn Sie versuchen, sie zu verwenden. Versuchen Sie beispielsweise, ein Codeobjekt mit nur einemRETURN_VALUE
mit nichts auf dem Stapel (Bytecodeb'S\0'
für 3.6+,b'S'
vorher) oder mit einem leeren Tupel zu erstellen,co_consts
wenn sich einLOAD_CONST 0
Bytecode im Bytecode befindet, oder mit einemvarnames
Dekrement von 1, damit der höchsteLOAD_FAST
tatsächlich eine Freevar lädt / cellvar cell. Für echten Spaß, wenn Sie daslnotab
Falsche genug bekommen, wird Ihr Code nur dann fehlerhaft, wenn er im Debugger ausgeführt wird.Verwenden
bytecode
oderbyteplay
schützen Sie nicht vor all diesen Problemen, aber es gibt einige grundlegende Überprüfungen der Integrität und nette Helfer, mit denen Sie beispielsweise einen Teil des Codes einfügen und sich Gedanken über die Aktualisierung aller Offsets und Beschriftungen machen können, damit Sie dies tun können. ' Versteh es nicht falsch und so weiter. (Außerdem verhindern sie, dass Sie diesen lächerlichen 6-Zeilen-Konstruktor eingeben und die dummen Tippfehler, die daraus entstehen, debuggen müssen.)Nun zu # 2.
Ich erwähnte, dass Codeobjekte unveränderlich sind. Und natürlich sind die Konstanten ein Tupel, also können wir das nicht direkt ändern. Und das Ding im const-Tupel ist ein String, den wir auch nicht direkt ändern können. Deshalb musste ich eine neue Zeichenfolge erstellen, um ein neues Tupel zu erstellen, um ein neues Codeobjekt zu erstellen.
Aber was wäre, wenn Sie eine Zeichenfolge direkt ändern könnten?
Nun, tief genug unter der Decke ist alles nur ein Zeiger auf einige C-Daten, oder? Wenn Sie CPython verwenden, gibt es eine C-API für den Zugriff auf die Objekte , und Sie können über
ctypes
Python selbst auf diese API zugreifen.pythonapi
Dies ist eine so schreckliche Idee, dass sie genau dort imctypes
Modul der stdlib abgelegt werden . :) Der wichtigste Trick, den Sie wissen müssen,id(x)
ist der tatsächliche Zeiger aufx
im Speicher (alsint
).Leider können wir mit der C-API für Zeichenfolgen nicht sicher in den internen Speicher einer bereits eingefrorenen Zeichenfolge gelangen. Also sicher schrauben, lasst uns einfach die Header-Dateien lesen und diesen Speicher selbst finden.
Wenn Sie CPython 3.4 - 3.7 verwenden (es ist anders für ältere Versionen und wer weiß für die Zukunft), wird ein Zeichenfolgenliteral aus einem Modul, das aus reinem ASCII besteht, im kompakten ASCII-Format gespeichert, dh der Struktur endet früh und der Puffer von ASCII-Bytes folgt sofort im Speicher. Dies wird (wie in wahrscheinlich segfault) unterbrochen, wenn Sie ein Nicht-ASCII-Zeichen in die Zeichenfolge oder bestimmte Arten von nicht-wörtlichen Zeichenfolgen einfügen. Sie können jedoch die anderen vier Möglichkeiten für den Zugriff auf den Puffer für verschiedene Arten von Zeichenfolgen nachlesen.
Um die Sache etwas einfacher zu machen, verwende ich das
superhackyinternals
Projekt von meinem GitHub. (Es ist absichtlich nicht pip-installierbar, da Sie dies wirklich nicht verwenden sollten, außer um mit Ihrem lokalen Build des Interpreters und dergleichen zu experimentieren.)Wenn Sie mit diesem Zeug spielen wollen,
int
ist es unter der Decke viel einfacher alsstr
. Und es ist viel einfacher zu erraten, was Sie durch Ändern des Werts von2
auf brechen können1
, oder? Vergiss die Vorstellung, lass es uns einfach tun (mit den Typen vonsuperhackyinternals
wieder):… Stellen Sie sich vor, dass das Codefeld eine Bildlaufleiste mit unendlicher Länge hat.
Ich habe dasselbe in IPython versucht, und als ich das erste Mal versuchte,
2
an der Eingabeaufforderung auszuwerten , ging es in eine Art unterbrechungsfreie Endlosschleife. Vermutlich verwendet es die Nummer2
für etwas in seiner REPL-Schleife, während der Aktieninterpreter dies nicht tut?quelle
PyUnicodeObject
, das ist wahrscheinlich wirklich nur Python in dem Sinne, dass ein Python-Interpreter es ausführen wird…NameError: name 'arg' is not defined
. Meinten Sie :args = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]
? Ein wohl besserer Weg, dies zu schreiben, wäre :args = [str(arg).replace('cat', 'dog') for arg in args]
. Eine weitere, noch kürzere Option :args = map(lambda a: str(a).replace('cat', 'dog'), args)
. Dies hat den zusätzlichen Vorteil, dassargs
es faul ist (was auch erreicht werden könnte, indem das obige Listenverständnis durch ein Generatorverständnis ersetzt wird -*args
funktioniert so oder so).PyUnicodeObject
Strukturdefinition, aber das Kopieren in die Antwort würde meiner Meinung nach nur stören , und ich denke, dass die Readme- und / oder Quellkommentaresuperhackyinternals
tatsächlich erklären, wie man auf den Puffer zugreift (zumindest) gut genug, um mich das nächste Mal daran zu erinnern; ich bin mir nicht sicher, ob es für irgendjemanden anderen ausreicht…), auf den ich hier nicht eingehen wollte. Der relevante Teil ist, wie man von einem Live-Python-Objekt zu seinerPyObject *
Via gelangtctypes
. (Und vielleicht Zeigerarithmetik simulieren, automatischechar_p
Konvertierungen vermeiden usw.)print
an einen Namen gebunden . Sie können auch den Namenprint
für sie binden :import yourmodule; yourmodule.print = badprint
.Affenpflaster
print
print
ist eine eingebaute Funktion, dieprint
die imbuiltins
Modul (oder__builtin__
in Python 2) definierte Funktion verwendet . Wenn Sie also das Verhalten einer integrierten Funktion ändern oder ändern möchten, können Sie den Namen in diesem Modul einfach neu zuweisen.Dieser Vorgang wird aufgerufen
monkey-patching
.Danach wird jeder
print
Anruf durchlaufencustom_print
, auch wenn sich derprint
in einem externen Modul befindet.Sie möchten jedoch nicht wirklich zusätzlichen Text drucken, sondern den gedruckten Text ändern. Eine Möglichkeit, dies zu erreichen, besteht darin, es in der Zeichenfolge zu ersetzen, die gedruckt werden soll:
Und in der Tat, wenn Sie laufen:
Oder wenn Sie das in eine Datei schreiben:
test_file.py
und importiere es:
Es funktioniert also wirklich wie beabsichtigt.
Wenn Sie jedoch nur vorübergehend einen Affen-Patch drucken möchten, können Sie dies in einen Kontext-Manager einbinden:
Wenn Sie also ausführen, hängt es vom Kontext ab, was gedruckt wird:
So könnte man also
print
durch Affen-Patches "hacken" .Ändern Sie das Ziel anstelle des
print
Wenn Sie sich die Signatur von ansehen, werden
print
Sie einfile
Argument bemerken , dassys.stdout
standardmäßig verwendet wird. Beachten Sie, dass dies ein dynamisches Standardargument ist (es wird bei jedem Aufruf wirklich nachgeschlagen ) und nicht wie normale Standardargumente in Python. Wenn Sie also ändern, wird das Drucken auf dem anderen Ziel noch praktischer, da Python auch eine Funktion bereitstellt (ab Python 3.4 ist es jedoch einfach, eine äquivalente Funktion für frühere Python-Versionen zu erstellen).sys.stdout
print
sys.stdout
print
redirect_stdout
Der Nachteil ist, dass es bei
print
Anweisungen, die nicht gedruckt werden, nicht funktioniertsys.stdout
und dass das Erstellen eigener Anweisungenstdout
nicht wirklich einfach ist.Dies funktioniert jedoch auch:
Zusammenfassung
Einige dieser Punkte wurden bereits von @abarnet erwähnt, aber ich wollte diese Optionen genauer untersuchen. Insbesondere, wie man es modulübergreifend ändert (mit
builtins
/__builtin__
) und wie man diese Änderung nur vorübergehend vornimmt (mit Kontextmanagern).quelle
redirect_stdout
, ist , dass es eine schöne Antwort ist, die dazu führt.Eine einfache Möglichkeit, alle Ausgaben von a zu erfassen
print
Funktion und anschließend zu verarbeiten, besteht darin, den Ausgabestream in eine andere Funktion zu ändern, z. B. eine Datei.Ich werde eine
PHP
Namenskonvention verwenden ( ob_start , ob_get_contents , ...)Verwendung:
Würde drucken
quelle
Kombinieren wir dies mit Frame-Introspektion!
Sie werden feststellen, dass dieser Trick jeder Begrüßung die aufrufende Funktion oder Methode voranstellt. Dies kann sehr nützlich für die Protokollierung oder das Debuggen sein. vor allem, weil Sie damit Druckanweisungen im Code von Drittanbietern "entführen" können.
quelle