Uwaga: to pytanie służy wyłącznie celom informacyjnym. Interesuje mnie, jak głęboko w wewnętrzne elementy Pythona można się z tym pogodzić.
Nie tak dawno temu rozpoczęła się dyskusja w ramach pewnego pytania dotyczącego tego, czy łańcuchy przekazane do instrukcji print mogą być modyfikowane po / w trakcie wywołania funkcji print
. Na przykład rozważmy funkcję:
def print_something():
print('This cat was scared.')
Teraz, kiedy print
jest uruchomiony, wyjście do terminala powinno wyświetlić:
This dog was scared.
Zwróć uwagę, że słowo „kot” zostało zastąpione słowem „pies”. Coś gdzieś było w stanie zmodyfikować te wewnętrzne bufory, aby zmienić to, co zostało wydrukowane. Załóżmy, że dzieje się to bez wyraźnej zgody autora oryginalnego kodu (stąd włamanie / przejęcie).
Szczególnie ten komentarz mądrego @abarnert skłonił mnie do myślenia:
Jest na to kilka sposobów, ale wszystkie są bardzo brzydkie i nigdy nie powinno się ich robić. Najmniej brzydkim sposobem jest prawdopodobnie zastąpienie
code
obiektu wewnątrz funkcji obiektem z innąco_consts
listą. Następnym krokiem jest prawdopodobnie sięgnięcie do C API w celu uzyskania dostępu do wewnętrznego bufora str. […]
Wygląda więc na to, że jest to rzeczywiście możliwe.
Oto mój naiwny sposób podejścia do tego problemu:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Oczywiście exec
jest źle, ale to tak naprawdę nie odpowiada na pytanie, ponieważ tak naprawdę nie modyfikuje niczego podczas print
wywołania / po .
Jak by to zrobić, jak wyjaśnił @abarnert?
42
na23
ponad dlaczego jest to zły pomysł, aby zmienić wartość"My name is Y"
na"My name is X"
.Odpowiedzi:
Po pierwsze, w rzeczywistości istnieje znacznie mniej hakerski sposób. Chcemy tylko zmienić jakie
print
wydruki, prawda?Lub, podobnie,
sys.stdout
zamiast tego możesz użyć monkeypatchprint
.Nie ma też nic złego w
exec … getsource …
pomyśle. Cóż, oczywiście jest w tym wiele nie tak, ale mniej niż to, co następuje tutaj…Ale jeśli chcesz zmodyfikować stałe kodu obiektu funkcji, możemy to zrobić.
Jeśli naprawdę chcesz naprawdę bawić się obiektami kodu, powinieneś użyć biblioteki takiej jak
bytecode
(po zakończeniu) lubbyteplay
(do tego czasu lub dla starszych wersji Pythona) zamiast robić to ręcznie. Nawet w przypadku czegoś tak trywialnegoCodeType
inicjalizacja jest uciążliwa; jeśli naprawdę musisz zrobić coś takiego jak naprawianielnotab
, tylko szaleniec zrobiłby to ręcznie.Ponadto jest oczywiste, że nie wszystkie implementacje Pythona używają obiektów kodu w stylu CPythona. Ten kod będzie działał w CPythonie 3.7 i prawdopodobnie we wszystkich wersjach co najmniej 2.2 z kilkoma drobnymi zmianami (nie dotyczy to hakowania kodu, ale rzeczy takie jak wyrażenia generatora), ale nie będzie działać z żadną wersją IronPython.
Co może pójść nie tak z hakowaniem obiektów kodu? Przeważnie to zwykłe segfaulty,
RuntimeError
które pochłaniają cały stack, bardziej normalne,RuntimeError
które można obsłużyć, lub śmieciowe wartości, które prawdopodobnie po prostu podniosą aTypeError
lubAttributeError
gdy spróbujesz ich użyć. Na przykład spróbuj utworzyć obiekt kodu zawierający tylko znakRETURN_VALUE
z niczym na stosie (kod bajtowyb'S\0'
dla 3.6+,b'S'
wcześniej) lub z pustą krotką,co_consts
gdy w kodzie bajtowym znajduje się znakLOAD_CONST 0
lub zvarnames
dekrementacją o 1, aby najwyższyLOAD_FAST
faktycznie ładował freevar / cellvar cell. Dla prawdziwej zabawy, jeślilnotab
pomylisz się wystarczająco, twój kod będzie segfaultowany tylko wtedy, gdy zostanie uruchomiony w debugerze.Używanie
bytecode
lubbyteplay
nie ochroni Cię przed wszystkimi tymi problemami, ale mają kilka podstawowych testów poczytalności i fajnych pomocników, które pozwalają ci zrobić takie rzeczy, jak wstawienie kawałka kodu i niech martwi się o aktualizację wszystkich przesunięć i etykiet, abyś mógł '' nie zrozumiem tego źle i tak dalej. (Poza tym nie musisz wpisywać tego śmiesznego 6-liniowego konstruktora i debugować głupie literówki, które z tego wynikają).Teraz przejdźmy do # 2.
Wspomniałem, że obiekty kodu są niezmienne. Oczywiście stałe są krotką, więc nie możemy tego bezpośrednio zmienić. A rzeczą w stałej krotce jest łańcuch, którego również nie możemy bezpośrednio zmienić. Dlatego musiałem zbudować nowy ciąg, aby zbudować nową krotkę i zbudować nowy obiekt kodu.
Ale co by było, gdybyś mógł bezpośrednio zmienić ciąg?
Cóż, wystarczająco głęboko pod kołdrą, wszystko jest tylko wskaźnikiem do niektórych danych w C, prawda? Jeśli używasz CPythona, istnieje C API, aby uzyskać dostęp do obiektów , i możesz go użyć,
ctypes
aby uzyskać dostęp do tego API z samego Pythona, co jest tak okropnym pomysłem, że umieścilipythonapi
tam bezpośrednio wctypes
module stdlib . :) Najważniejszą sztuczką, którą musisz wiedzieć,id(x)
jest faktyczny wskaźnikx
w pamięci (jakoint
).Niestety, C API dla stringów nie pozwala nam bezpiecznie dostać się do wewnętrznej pamięci już zamrożonego łańcucha. Więc chrzanić bezpiecznie, po prostu przeczytajmy pliki nagłówkowe i sami znajdźmy to miejsce .
Jeśli używasz CPython 3.4 - 3.7 (jest inny dla starszych wersji i kto wie na przyszłość), literał ciągu z modułu, który jest wykonany z czystego ASCII, będzie przechowywany w kompaktowym formacie ASCII, co oznacza, że struktura kończy się wcześniej, a bufor bajtów ASCII następuje natychmiast w pamięci. To się zepsuje (jak w prawdopodobnie segfault), jeśli umieścisz w ciągu znak inny niż ASCII lub pewne rodzaje nieliteralnych ciągów, ale możesz przeczytać pozostałe 4 sposoby dostępu do bufora dla różnych rodzajów ciągów.
Aby trochę ułatwić, używam
superhackyinternals
projektu poza moim GitHubem. (Celowo nie można go zainstalować za pomocą pip, ponieważ naprawdę nie powinieneś go używać, z wyjątkiem eksperymentowania z lokalną kompilacją interpretera i tym podobnymi).Jeśli chcesz się tym bawić, pod kołdrą
int
jest o wiele prostsze niżstr
. O wiele łatwiej jest zgadnąć, co można złamać, zmieniając wartość2
na1
, prawda? Właściwie zapomnij o wyobrażeniach, po prostu zróbmy to (używającsuperhackyinternals
ponownie typów z ):… Udawaj, że skrzynka z kodem ma pasek przewijania o nieskończonej długości.
Wypróbowałem to samo w IPythonie i kiedy pierwszy raz spróbowałem ocenić
2
w zachęcie, wszedł on w jakąś nieprzerwaną nieskończoną pętlę. Prawdopodobnie używa numeru2
do czegoś w swojej pętli REPL, podczas gdy interpreter zapasów nie?źródło
PyUnicodeObject
Z drugiej strony, dostęp do pamięci wewnętrznej a , to prawdopodobnie tak naprawdę tylko Python w tym sensie, że interpreter Pythona będzie go uruchamiał…NameError: name 'arg' is not defined
. Czy chodziło Ci oargs = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]
? Zapewne lepszy sposób napisać to będzie:args = [str(arg).replace('cat', 'dog') for arg in args]
. Innym, jeszcze krócej, opcjonalnie:args = map(lambda a: str(a).replace('cat', 'dog'), args)
. Ma to dodatkową zaletę, żeargs
jest leniwy (co można również osiągnąć poprzez zastąpienie powyższego rozumienia list z generatorem -*args
działa w obu przypadkach).PyUnicodeObject
definicji struktury, ale skopiowanie jej do odpowiedzi mogłoby po prostu przeszkodzić i myślę, że komentarze readme i / lub źródłasuperhackyinternals
wyjaśniają, jak uzyskać dostęp do bufora (przynajmniej wystarczająco dobrze, aby przypomnieć mi następnym razem, gdy mi zależy; nie jestem pewien, czy wystarczy to komukolwiek innemu…), o czym nie chciałem się tutaj dostać. Istotna część dotyczy tego, jak przejść z aktywnego obiektu Pythona do jegoPyObject *
viactypes
. (I może symulując arytmetykę wskaźnika, unikając automatycznychchar_p
konwersji itp.)print
z nią połączą. Można także powiązać nazwęprint
dla nichimport yourmodule; yourmodule.print = badprint
.Łatka małpy
print
print
jest funkcją wbudowaną, więc użyjeprint
funkcji zdefiniowanej wbuiltins
module (lub__builtin__
w Pythonie 2). Więc za każdym razem, gdy chcesz zmodyfikować lub zmienić zachowanie funkcji wbudowanej, możesz po prostu ponownie przypisać nazwę w tym module.Ten proces nazywa się
monkey-patching
.Po tym każde
print
połączenie będzie przekazywanecustom_print
, nawet jeśliprint
jest w module zewnętrznym.Jednak tak naprawdę nie chcesz drukować dodatkowego tekstu, chcesz zmienić drukowany tekst. Jednym ze sposobów jest zastąpienie go w ciągu, który zostałby wydrukowany:
I rzeczywiście, jeśli biegniesz:
Lub jeśli zapiszesz to do pliku:
plik_testowy.py
i zaimportuj:
Więc to naprawdę działa zgodnie z przeznaczeniem.
Jednak w przypadku, gdy chcesz tylko tymczasowo drukować małpy, możesz umieścić to w menedżerze kontekstu:
Więc po uruchomieniu zależy to od kontekstu, co jest drukowane:
Więc w ten sposób można "hakować"
print
przez małpowanie.Zmodyfikuj cel zamiast
print
Jeśli spojrzysz na podpis
print
, zauważyszfile
argument, który jestsys.stdout
domyślny. Zauważ, że jest to dynamiczny argument domyślny ( naprawdę wygląda w górę zasys.stdout
każdym razem, gdy dzwoniszprint
), a nie jak zwykłe domyślne argumenty w Pythonie. Więc jeśli zmieniszsys.stdout
print
, faktycznie wydrukujesz do innego celu, jeszcze wygodniej, że Python również zapewniaredirect_stdout
funkcję (od Pythona 3.4, ale łatwo jest utworzyć równoważną funkcję dla wcześniejszych wersji Pythona).Wadą jest to, że nie zadziała w przypadku
print
instrukcji, które nie są drukowane,sys.stdout
a tworzenie własnychstdout
nie jest naprawdę proste.Jednak działa to również:
Podsumowanie
@Abarnet wspomniał już o niektórych z tych punktów, ale chciałem zbadać te opcje bardziej szczegółowo. Zwłaszcza jak zmodyfikować to w modułach (używając
builtins
/__builtin__
) i jak uczynić tę zmianę tylko tymczasową (używając menedżerów kontekstu).źródło
redirect_stdout
, więc miło jest mieć jasną odpowiedź, która do tego prowadzi.Prostym sposobem na przechwycenie całego wyjścia
print
funkcji, a następnie jego przetworzenie, jest zmiana strumienia wyjściowego na coś innego, np. Plik.Użyję
PHP
konwencje nazewnictwa ( ob_start , ob_get_contents , ...)Stosowanie:
Wydrukowałoby
źródło
Połączmy to z introspekcją ramek!
Przekonasz się, że ta sztuczka poprzedza każde powitanie funkcją lub metodą wywołującą. Może to być bardzo przydatne do logowania lub debugowania; zwłaszcza, że pozwala "przechwytywać" instrukcje drukowania w kodzie strony trzeciej.
źródło