[解決済み] Pythonのprint関数を "ハック "することは可能か?
質問
注:この質問は情報提供のみを目的としています。Pythonの内部をどれだけ深く掘り下げることができるかに興味があります。
それほど昔ではないのですが、ある
質問
の呼び出しの後/最中に、print文に渡される文字列を変更できるかどうかについて、ある質問の中で議論が始まりました。
print
の呼び出しが行われた後/中に、print文に渡された文字列を変更できるかどうかという質問
def print_something():
print('This cat was scared.')
さて、ここで
print
を実行すると、ターミナルへの出力が表示されるはずです。
This dog was scared.
cat" という単語が "dog" という単語で置き換えられていることに注目してください。どこかの誰かが、印刷されたものを変更するために、これらの内部バッファを修正することができたのです。これは、元のコードの著者の明示的な許可なしに行われたと仮定してください (したがって、ハッキング/ハイジャック)。
これは コメント は、特に賢明な@abarnertから、私に考えさせました。
これを行う方法はいくつかありますが、どれも非常に醜いもので、決して行うべきではありません。 決してやってはいけないことです。最も醜くない方法は、おそらく
code
オブジェクトを別のco_consts
のリストが表示されます。次に、おそらく C API に到達して str の内部バッファにアクセスします。 内部バッファにアクセスすることです。[...]
ということで、実際に可能なようです。
この問題にアプローチする私の素朴な方法を紹介します。
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
もちろん
exec
は悪いものですが、それは実際には何も修正しないので、質問には答えられません。
の間、または
print
が呼び出されます。
@abarnertさんの説明の通りだと、どうなるのでしょうか?
どのように解決するのですか?
まず、実際にはもっと簡単な方法があります。私たちがしたいのは
print
が表示する内容を変更したいだけなのです、そうでしょう?
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
また、同様にモンキーパッチで
sys.stdout
の代わりに
print
.
また、何も問題なく
exec … getsource …
の考え方もあります。まあ、もちろん
たっぷり
がありますが、ここに続くものよりは少ないでしょう...。
しかし、もしあなたが関数オブジェクトのコード定数を修正したいのであれば、それは可能です。
本当にコードオブジェクトで遊びたいのであれば、以下のようなライブラリを使うべきです。
bytecode
(完成したら) または
byteplay
(それまで、あるいは古いPythonのバージョンのために)それを手動で行う代わりに。このような些細なことでさえ
CodeType
を修正するようなことを実際に行う必要があるのなら、それは苦痛です。
lnotab
を修正するようなことを実際に行う必要がある場合、精神異常者だけがそれを手動で行うでしょう。
また、すべての Python の実装が CPython スタイルのコードオブジェクトを使用しているわけではないことは言うまでもありません。このコードはCPython 3.7、そしておそらくいくつかの小さな変更で少なくとも2.2までのすべてのバージョンで動作します(そしてコードハック的なものでなく、ジェネレータ式のようなもの)が、IronPythonのどのバージョンでも動作するわけではありません。
import types
def print_function():
print ("This cat was scared.")
def main():
# A function object is a wrapper around a code object, with
# a bit of extra stuff like default values and closure cells.
# See inspect module docs for more details.
co = print_function.__code__
# A code object is a wrapper around a string of bytecode, with a
# whole bunch of extra stuff, including a list of constants used
# by that bytecode. Again see inspect module docs. Anyway, inside
# the bytecode for string (which you can read by typing
# dis.dis(string) in your REPL), there's going to be an
# instruction like LOAD_CONST 1 to load the string literal onto
# the stack to pass to the print function, and that works by just
# reading co.co_consts[1]. So, that's what we want to change.
consts = tuple(c.replace("cat", "dog") if isinstance(c, str) else c
for c in co.co_consts)
# Unfortunately, code objects are immutable, so we have to create
# a new one, copying over everything except for co_consts, which
# we'll replace. And the initializer has a zillion parameters.
# Try help(types.CodeType) at the REPL to see the whole list.
co = types.CodeType(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
consts, co.co_names, co.co_varnames, co.co_filename,
co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
print_function.__code__ = co
print_function()
main()
コードオブジェクトをハックすると、何か悪いことが起こるのでしょうか?ほとんどがセグメンテーションエラーです。
RuntimeError
がスタック全体を食いつぶしてしまうこと、もっと普通の
RuntimeError
を処理することができるもの、 あるいは
TypeError
または
AttributeError
を使おうとすると 例えば、コードオブジェクトを作るときに
RETURN_VALUE
だけで、スタックには何もない状態(バイトコードの
b'S\0'
3.6+ では
b'S'
以前は)、あるいは、空のタプルで
co_consts
がある場合は
LOAD_CONST 0
がある場合は、バイトコードに
varnames
が 1 つ減算されるため、最も高い
LOAD_FAST
は実際にフリーバー/セルバーのセルをロードします。本当に楽しいのは、もしあなたが
lnotab
を十分に間違えると、デバッガで実行したときだけコードがセグメンテーションフォルトになります。
使用方法
bytecode
または
byteplay
は、これらの問題のすべてからあなたを守ることはできませんが、いくつかの基本的なサニティチェックと、コードの塊を挿入して、あなたが間違えないようにすべてのオフセットとラベルの更新を心配させるようなことをさせる素晴らしいヘルパーを備えています。(さらに、馬鹿げた6行のコンストラクタを入力したり、そのために生じる愚かなタイプミスをデバッグする必要もありません)。
次は2番です。
コードオブジェクトはイミュータブルであると書きました。そしてもちろんconstはタプルなので、それを直接変更することはできません。そしてconstタプル内のものは文字列で、これも直接変更することはできません。だから、新しいコードオブジェクトを作るために、新しいタプルを作るために、新しい文字列を作らなければならないのです。
しかし、文字列を直接変更できるとしたらどうでしょうか?
まあ、蓋を開けてみれば、全てはあるC言語のデータへのポインタに過ぎないわけですがね。CPythonを使っているのであれば
オブジェクトにアクセスするためのC言語のAPI
で、そして
を使用することができます。
ctypes
を使って Python 自身からその API にアクセスすることができますが、これはとてもひどい考えで、Python に
pythonapi
を標準ライブラリの
ctypes
モジュールにあります。
. :) 知っておくべき最も重要なトリックは
id(x)
への実際のポインタであるということです。
x
への実際のポインタは、メモリ上の
int
).
残念ながら、文字列のためのCのAPIは、すでに凍結された文字列の内部ストレージを安全に取得することを許しません。ですから、安全にねじ込むには、単に ヘッダーファイルを読む を読んで、自分自身でそのストレージを見つけましょう。
CPython 3.4 - 3.7 を使用している場合 (古いバージョンでは異なりますし、将来のこともわかりません)、純粋な ASCII でできているモジュールからの文字列リテラルは、コンパクト ASCII フォーマットを使用して保存されます。文字列に非 ASCII 文字を入れたり、ある種の非リテラル文字列を入れたりすると、これは(おそらくセグメンテーションのように)壊れますが、異なる種類の文字列に対してバッファにアクセスする他の 4 つの方法については、こちらをお読みください。
少し簡単にするために、私は
superhackyinternals
プロジェクトを使っています。(意図的に pip-installable ではないので、インタプリタなどのローカルビルドの実験以外には、これを使うべきではありません)。
import ctypes
import internals # https://github.com/abarnert/superhackyinternals/blob/master/internals.py
def print_function():
print ("This cat was scared.")
def main():
for c in print_function.__code__.co_consts:
if isinstance(c, str):
idx = c.find('cat')
if idx != -1:
# Too much to explain here; just guess and learn to
# love the segfaults...
p = internals.PyUnicodeObject.from_address(id(c))
assert p.compact and p.ascii
addr = id(c) + internals.PyUnicodeObject.utf8_length.offset
buf = (ctypes.c_int8 * 3).from_address(addr + idx)
buf[:3] = b'dog'
print_function()
main()
こんなので遊びたいなら
int
よりもずっとシンプルです。
str
. の値を変更することで何が壊れるかを推測するのはずっと簡単です。
2
を
1
という具合になりますよね?実は、想像するのはやめて、実際にやってみましょう。
superhackyinternals
の型を使って)やってみましょう。
>>> n = 2
>>> pn = PyLongObject.from_address(id(n))
>>> pn.ob_digit[0]
2
>>> pn.ob_digit[0] = 1
>>> 2
1
>>> n * 3
3
>>> i = 10
>>> while i < 40:
... i *= 2
... print(i)
10
10
10
... そのコードボックスには無限の長さのスクロールバーがあることにしてください。
同じことをIPythonで試してみましたが、最初に評価しようとしたのは
2
を評価しようとした最初のとき、ある種の中断できない無限ループに入りました。おそらくそれは、数値
2
を REPL ループの何かに使っているのでしょう。
関連
-
[解決済み】「OverflowError: Python int too large to convert to C long" on windows but not mac
-
[解決済み] 関数デコレータを作成し、それらを連鎖させるには?
-
[解決済み] 関数内でグローバル変数を使用する
-
[解決済み] Pythonのリストメソッドであるappendとextendの違いは何ですか?
-
[解決済み] Pythonで静的なクラス変数は可能ですか?
-
[解決済み] 改行やスペースを入れずに印刷する方法
-
[解決済み] Java の配列を表示する最も簡単な方法は何ですか?
-
[解決済み] モジュールの関数名(文字列)を使って、モジュールの関数を呼び出す。
-
[解決済み] Pythonで標準エラー出力するには?
-
[解決済み] print関数の出力をフラッシュする(pythonの出力をバッファリング解除する)にはどうすればよいですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Pythonの非常に便利な2つのデコレーターを解説
-
Python 人工知能 人間学習 描画 機械学習モデル作成
-
[解決済み】お使いのCPUは、このTensorFlowバイナリが使用するようにコンパイルされていない命令をサポートしています。AVX AVX2
-
[解決済み】RuntimeWarning: 割り算で無効な値が発生しました。
-
[解決済み】Pythonスクリプトで「Expected 2D array, got 1D array instead: 」というエラーが発生?
-
[解決済み】Django: ImproperlyConfigured: SECRET_KEY 設定は空であってはならない
-
[解決済み】LogisticRegression: Pythonでsklearnを使用して、未知のラベルタイプ: '連続'を使用しています。
-
[解決済み] TypeError: 'DataFrame' オブジェクトは呼び出し可能ではない
-
[解決済み】Python: SyntaxError: キーワードは式になり得ない
-
[解決済み】「OverflowError: Python int too large to convert to C long" on windows but not mac