Python 2.7にダイナミズムを追加するには?

dictクラスにフィールドを追加したいと思ったことはありますか? len(action.name)代わりにaction.name.len()を書くことを夢見ていますか? お気に入りのPythonに柔軟性を追加しますか? これは不可能だと言われていますか? それでは、Pythonオブジェクトモデルの詳細を見ていきましょう。

Python 2.7では、すべての組み込みクラスおよびCで記述されたクラスは不変です。 つまり、組み込み型のメソッドまたはフィールドを削除/追加/置換することはできません。 しかし同時に、純粋なPythonで作成されたクラスは、実行時に完全に変更できます。
注:この記事では、新しいスタイルのクラスについて説明します。 新しいスタイルと古いスタイルの違いについては、公式ドキュメントwww.python.org/doc/newstyleを参照してください。

例:
 class foo(object): def getA(self): return "A" x = foo() print x.getA() # “A” def getB(obj): return "B" foo.getA = getB #  print x.getA() # “B” 


しかし、listまたはdictクラスで同様のトリックを実行すると失敗します。

>>> list.length = len
TypeError: can't set attributes of built-in/extension type 'list'


この動作は偶然ではありません。 一見、 listfooは同じtypeメタクラスのインスタンスです。 しかし、Pythonインタープリターはこれら2つのタイプを区別し、クラスメンバーのリストを変更しようとするときに異なる動作を提供します。

どうして?


公式バージョンがあります: ここ (最後の段落)、 ここまたはここの Guido van Rossumの意見。 一言で言えば、同じアドレス空間にある複数のPythonインタープリターに問題があります。
障害の1つは、インラインメソッドを置き換える問題でもあります。 たとえば、 string.__len__を独自の実装に置き換えた場合、この変更はCで記述されたPythonモジュールに反映されません。APIの観点から、PyString_Size(...)関数は変更されません。 このような不協和音は、微妙なバグやあいまいな行動につながる可能性があります。

どうする


できないが、本当にしたい場合は... Python 2.7のソースコードを取得します(http://hg.python.org/cpython/を「2.7」ブランチに切り替えます)。 例外をスローするコードを見つけるのは非常に簡単です。 「組み込み/拡張タイプの属性を設定できません」というテキストを探すだけです。 必要な行は、関数"type_setattro"ファイルtypeobject.cにあります。 この関数は、Pythonスクリプトがクラスのプロパティを追加または変更しようとしたときに呼び出されます。 関数は、 type.__setattr__として読み取り可能type.__setattr__ 。 私たちを妨げる制限を削除するには、このメソッドを独自のより忠実な実装に置き換える必要があります。
Pythonスクリプトからこれを行うことはできません。 type.__setattr__をオーバーライドしようとすると、よく知られた例外が発生します。

TypeError: can't set attributes of built-in/extension type 'type'

ただし、C番目のモジュールを記述してtypeオブジェクトにアクセスする場合、関数"type_setattro"へのポインターの代わりに、独自のバージョンの__setattr__メソッドへのポインターに置き換えることができます。

さあ始めましょう!


PythonモジュールをCで記述する方法を既に知っていることを願っています。標準のドキュメントでは、これを行う方法が非常によく説明されています(http://docs.python.org/extending/extending.html)。 モジュールには、関数、クラス、フィールドはありません。 インタープリターがモジュールをインポートすると、すべての魔法が発生します。

 #include <Python.h> static setattrofunc original_setattr_func = NULL; PyMODINIT_FUNC inittypehack(void) { PyObject *m; m = Py_InitModule("typehack", NULL); if (m == NULL) return; apply_patch(); } void apply_patch() { original_setattr_func = PyType_Type.tp_setattro; //   __setattr__ PyType_Type.tp_setattro = new_setattr_func; //  __setattr__  } 


PyType_Typeは、 typeメタクラスに関するすべての情報(名前、メモリ内のオブジェクトのサイズ、フラグ)を格納する構造です。 特に、メタクラスの特定のメソッドを実装する関数へのポインターを保存します。

以上です。 new_setattr_funcの実装をnew_setattr_funcことは残っています。 ここではすべてのコードを提供しません。 作業のロジックのみを説明します。
  1. 既存のフィールドとメソッドは変更できません。 自分で追加することしかできません。
  2. クラスに新しい属性を追加すると、 __dyn_attrs__フィールドが__dyn_attrs__ 、追加されたすべての属性の名前を含む文字列が保存されます。 将来的には、このリストの属性のみを置き換えることが可能になります。 これは愚か者からのそのような保護であり、100%の保証を与えるものではありませんが、元の属性をそのまま保持するのに役立ちます。
  3. クラスの属性を置換しようとすると、変更される属性の名前が__dyn_attrs__リストにあることを__dyn_attrs__ます。 そうでない場合、例外がスローされます。
  4. クラス属性のリストを変更した後、 PyType_Modified(type)関数を呼び出してキャッシュを確実にクリアする必要があります。


Google Codeのプロジェクトのソースコードはこちらから入手できます
(すべてがひざまずいて行われているため、アセンブリスクリプトは添付しません。OSで* .cファイルをコンパイルする方法を知っていることを望みます)

利益?


今、あなたはこれらの奇跡を行うことができます:

>>> import typehack #god mode on
>>> def custom_len(text):
... return len(txt)
...
>>> list.size = custom_len # "size"
>>> ['Tinker', 'Tailor', 'Solder', 'Spy'].size()
4
>>> str.len = property(custom_len) # "len"
>>> "Hello".len
5


おわりに


そして結論は、Pythonは動的なプログラミング言語であるため、その動作はその場で変更できるということです。 Pythonオブジェクトモデルを使用すると、独自のバージョンのインタープリターを作成せずに、小さなプラグインを使用してこれらすべてを実行できます。 開かれた着物の原則が私たちの手にかかっています。

Pythonの魔法を学ぶために頑張ってください

PSクラス属性の削除を実装せず、すべての可能な問題を特定するための完全なテストを実施しませんでした。 これはほんの小さなハック、概念実証です。 属性の削除を実装することは、追加するよりもそれほど難しくないと思います。 また、Python 3への移植は深刻な問題を引き起こすことはありません。オブジェクトモデルは似ています。

Source: https://habr.com/ru/post/J142034/


All Articles