Catatan: Pertanyaan ini hanya untuk tujuan informasi. Saya tertarik untuk melihat seberapa dalam ke internal Python adalah mungkin untuk pergi dengan ini.
Belum lama ini, sebuah diskusi dimulai di dalam pertanyaan tertentu mengenai apakah string yang diteruskan ke pernyataan cetak dapat dimodifikasi setelah / selama panggilan print
telah dibuat. Misalnya, perhatikan fungsinya:
def print_something():
print('This cat was scared.')
Sekarang, ketika print
dijalankan, maka output ke terminal akan ditampilkan:
This dog was scared.
Perhatikan kata "kucing" telah digantikan oleh kata "anjing". Sesuatu di suatu tempat entah bagaimana dapat memodifikasi buffer internal untuk mengubah apa yang dicetak. Anggap ini dilakukan tanpa izin eksplisit dari pembuat kode asli (karenanya, peretasan / pembajakan).
Ini komentar dari @abarnert bijak, khususnya, membuat saya berpikir:
Ada beberapa cara untuk melakukan itu, tetapi semuanya sangat jelek, dan tidak boleh dilakukan. Cara paling jelek adalah mungkin mengganti
code
objek di dalam fungsi dengan satu denganco_consts
daftar yang berbeda . Selanjutnya mungkin menjangkau ke dalam API C untuk mengakses buffer internal str. [...]
Jadi, sepertinya ini benar-benar mungkin.
Inilah cara naif saya dalam mendekati masalah ini:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Tentu saja exec
itu buruk, tetapi itu tidak benar-benar menjawab pertanyaan, karena itu tidak benar-benar mengubah apa pun ketika / setelah print
dipanggil.
Bagaimana ini bisa dilakukan seperti yang dijelaskan @abarnert?
42
menjadi23
daripada mengapa itu adalah ide yang buruk untuk mengubah nilai"My name is Y"
menjadi"My name is X"
.Jawaban:
Pertama, sebenarnya ada cara yang jauh lebih tidak retas. Yang ingin kami lakukan adalah mengubah
print
cetakan apa , bukan?Atau, sama, Anda dapat monkeypatch
sys.stdout
bukanprint
.Juga, tidak ada yang salah dengan
exec … getsource …
idenya. Yah, tentu saja ada banyak yang salah dengan itu, tetapi kurang dari apa yang terjadi di sini ...Tetapi jika Anda ingin memodifikasi konstanta kode objek fungsi, kita bisa melakukannya.
Jika Anda benar-benar ingin bermain-main dengan objek kode secara nyata, Anda harus menggunakan perpustakaan seperti
bytecode
(ketika selesai) ataubyteplay
(sampai saat itu, atau untuk versi Python yang lebih lama) daripada melakukannya secara manual. Bahkan untuk sesuatu yang sepele ini,CodeType
penginisialisasi adalah rasa sakit; jika Anda benar-benar perlu melakukan hal-hal seperti memperbaikilnotab
, hanya orang gila yang akan melakukannya secara manual.Juga, tidak perlu dikatakan bahwa tidak semua implementasi Python menggunakan objek kode gaya CPython. Kode ini akan bekerja di CPython 3.7, dan mungkin semua versi kembali ke setidaknya 2.2 dengan beberapa perubahan kecil (dan bukan hal peretasan kode, tetapi hal-hal seperti ekspresi generator), tetapi tidak akan berfungsi dengan versi IronPython apa pun.
Apa yang salah dengan meretas objek kode? Sebagian besar hanya segfault,
RuntimeError
s yang memakan seluruh tumpukan, lebih banyakRuntimeError
s normal yang dapat ditangani, atau nilai-nilai sampah yang mungkin hanya akan menaikkanTypeError
atauAttributeError
ketika Anda mencoba menggunakannya. Sebagai contoh, coba buat objek kode hanyaRETURN_VALUE
dengan tanpa apa-apa di stack (bytecodeb'S\0'
for 3.6+,b'S'
sebelumnya), atau dengan tuple kosongco_consts
ketika adaLOAD_CONST 0
dalam bytecode, atau denganvarnames
decremented oleh 1 sehingga yang tertinggiLOAD_FAST
sebenarnya memuat freevar / sel cellvar. Untuk bersenang-senang nyata, jika Anda mendapatkanlnotab
kesalahan yang cukup, kode Anda hanya akan segfault ketika dijalankan di debugger.Menggunakan
bytecode
ataubyteplay
tidak akan melindungi Anda dari semua masalah itu, tetapi mereka memang memiliki beberapa pemeriksaan kewarasan dasar, dan pembantu yang baik yang memungkinkan Anda melakukan hal-hal seperti memasukkan sepotong kode dan membiarkannya khawatir tentang memperbarui semua offset dan label sehingga Anda dapat ' jangan salah, dan sebagainya. (Plus, mereka membuat Anda tidak perlu mengetikkan konstruktor 6-garis konyol itu, dan harus men-debug kesalahan ketik konyol yang muncul karena melakukan hal itu.)Sekarang ke # 2.
Saya menyebutkan bahwa objek kode tidak dapat diubah. Dan tentu saja const adalah tuple, jadi kita tidak bisa mengubahnya secara langsung. Dan hal dalam tuple const adalah string, yang juga tidak dapat kita ubah secara langsung. Itu sebabnya saya harus membuat string baru untuk membangun tuple baru untuk membangun objek kode baru.
Tetapi bagaimana jika Anda bisa mengubah string secara langsung?
Nah, cukup dalam di bawah selimut, semuanya hanya sebuah penunjuk ke beberapa data C, kan? Jika Anda menggunakan CPython, ada API C untuk mengakses objek , dan Anda dapat menggunakannya
ctypes
untuk mengakses API dari dalam Python itu sendiri, yang merupakan ide yang mengerikan sehingga mereka menempatkannyapythonapi
di sana dictypes
modul stdlib . :) Trik paling penting yang perlu Anda ketahui adalah ituid(x)
adalah pointer aktual kex
dalam memori (sebagaiint
).Sayangnya, API C untuk string tidak akan membiarkan kami dengan aman mendapatkan penyimpanan internal dari string yang sudah beku. Jadi sekrup aman, mari kita baca file header dan menemukan penyimpanan itu sendiri.
Jika Anda menggunakan CPython 3.4 - 3.7 (berbeda untuk versi yang lebih lama, dan siapa yang tahu untuk masa depan), string literal dari modul yang terbuat dari ASCII murni akan disimpan menggunakan format ASCII yang ringkas, yang berarti berakhir lebih awal dan buffer byte ASCII segera menyusul dalam memori. Ini akan pecah (seperti dalam mungkin segfault) jika Anda meletakkan karakter non-ASCII dalam string, atau jenis string non-literal tertentu, tetapi Anda dapat membaca tentang 4 cara lain untuk mengakses buffer untuk berbagai jenis string.
Untuk mempermudah, saya menggunakan
superhackyinternals
proyek dari GitHub saya. (Sengaja tidak dapat diinstal melalui pip karena Anda benar-benar tidak boleh menggunakan ini kecuali untuk bereksperimen dengan penerjemah lokal Anda dan sejenisnya.)Jika Anda ingin bermain dengan barang-barang ini,
int
jauh lebih sederhana di bawah selimut daripadastr
. Dan jauh lebih mudah untuk menebak apa yang bisa Anda hancurkan dengan mengubah nilai2
to1
, kan? Sebenarnya, lupakan membayangkan, mari kita lakukan saja (menggunakan tipe darisuperhackyinternals
lagi):... berpura-pura bahwa kotak kode memiliki bilah gulir panjang tak terbatas.
Saya mencoba hal yang sama di IPython, dan pertama kali saya mencoba untuk mengevaluasi
2
pada prompt, itu masuk ke semacam loop tak terbatas yang tidak terputus. Mungkin itu menggunakan nomor2
untuk sesuatu dalam loop REPL, sedangkan penerjemah saham tidak?sumber
PyUnicodeObject
, di sisi lain, itu mungkin benar-benar hanya Python dalam arti bahwa juru bahasa Python akan menjalankannya ...NameError: name 'arg' is not defined
. Apakah maksud Anda:args = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]
? Cara dibilang lebih baik untuk menulis ini akan menjadi:args = [str(arg).replace('cat', 'dog') for arg in args]
. Lain, bahkan lebih pendek, pilihan:args = map(lambda a: str(a).replace('cat', 'dog'), args)
. Ini memiliki manfaat tambahan yangargs
malas (yang juga bisa dicapai dengan mengganti pemahaman daftar di atas dengan generator satu -*args
bekerja dengan cara baik).PyUnicodeObject
definisi struct, tetapi menyalinnya ke dalam jawaban akan saya pikir hanya menghalangi, dan saya pikir readme dan / atau komentar sumber untuksuperhackyinternals
benar - benar menjelaskan cara mengakses buffer (setidaknya cukup baik untuk mengingatkan saya lain kali saya peduli; tidak yakin apakah itu akan cukup untuk orang lain ...), yang saya tidak ingin masuk ke sini. Bagian yang relevan adalah cara untuk mendapatkan dari objek Python langsung kePyObject *
viactypes
. (Dan mungkin mensimulasikan aritmatika pointer, menghindarichar_p
konversi otomatis , dll.)print
nama. Anda juga dapat mengikat namaprint
untuk mereka:import yourmodule; yourmodule.print = badprint
.Patch monyet
print
print
adalah fungsi bawaan sehingga akan menggunakanprint
fungsi yang ditentukan dalambuiltins
modul (atau__builtin__
dengan Python 2). Jadi, setiap kali Anda ingin memodifikasi atau mengubah perilaku fungsi builtin Anda dapat dengan mudah menetapkan kembali nama dalam modul itu.Proses ini disebut
monkey-patching
.Setelah itu setiap
print
panggilan akan melaluicustom_print
, bahkan jikaprint
ada dalam modul eksternal.Namun Anda tidak benar-benar ingin mencetak teks tambahan, Anda ingin mengubah teks yang dicetak. Salah satu cara untuk melakukannya adalah menggantinya dengan string yang akan dicetak:
Dan memang jika Anda menjalankan:
Atau jika Anda menulis itu ke file:
test_file.py
dan impor:
Jadi itu benar-benar berfungsi sebagaimana dimaksud.
Namun, jika Anda hanya ingin sementara mencetak monkey-patch Anda dapat membungkusnya dalam konteks-manajer:
Jadi ketika Anda menjalankannya tergantung pada konteks apa yang dicetak:
Jadi begitulah cara Anda bisa "meretas"
print
dengan menambal monyet.Ubah target alih-alih
print
Jika Anda melihat tanda tangan
print
Anda akan melihatfile
argumen yang secarasys.stdout
default. Perhatikan bahwa ini adalah argumen default dinamis (itu benar - benar terlihatsys.stdout
setiap kali Anda meneleponprint
) dan tidak seperti argumen default normal di Python. Jadi jika Anda mengubahsys.stdout
print
sebenarnya akan mencetak ke target yang berbeda bahkan lebih nyaman bahwa Python juga menyediakanredirect_stdout
fungsi (dari Python 3.4 on, tetapi mudah untuk membuat fungsi yang setara untuk versi Python sebelumnya).Kelemahannya adalah tidak akan berfungsi untuk
print
pernyataan yang tidak bisa dicetaksys.stdout
dan pembuatan sendiristdout
tidak terlalu mudah.Namun ini juga berfungsi:
Ringkasan
Beberapa poin ini sudah disebutkan oleh @abarnet tapi saya ingin menjelajahi opsi ini lebih detail. Terutama cara memodifikasinya lintas modul (menggunakan
builtins
/__builtin__
) dan bagaimana membuat perubahan itu hanya sementara (menggunakan manajer konteks).sumber
redirect_stdout
, jadi senang memiliki jawaban yang jelas yang mengarah ke sana.Cara sederhana untuk menangkap semua output dari suatu
print
fungsi dan kemudian memprosesnya, adalah mengubah aliran output ke sesuatu yang lain, misalnya file.Saya akan menggunakan
PHP
konvensi penamaan ( ob_start , ob_get_contents , ...)Pemakaian:
Akan mencetak
sumber
Mari kita gabungkan ini dengan introspeksi frame!
Anda akan menemukan trik ini untuk setiap salam dengan fungsi atau metode pemanggilan. Ini mungkin sangat berguna untuk logging atau debugging; terutama karena memungkinkan Anda "membajak" mencetak laporan dalam kode pihak ketiga.
sumber