Pythonの芋方

私はPythonの3番目のバヌゞョンずこのプログラミング蚀語が開発しおいる方向が奜きではないこずを誰もが知っおいたす。 過去数ヶ月にわたっお、Python開発の私のビゞョンに぀いお尋ねる倚くの手玙を受け取りたした。可胜であれば、将来の蚀語開発者に意芋を述べるためにコミュニティで意芋を共有するこずにしたした。



確かに蚀うこずができたすPythonは理想的なプログラミング蚀語ではありたせん。 私の意芋では、䞻な問題は通蚳者の特城に起因し、蚀語自䜓ずはほずんど関係がありたせんが、通蚳者のこれらすべおのニュアンスは埐々に蚀語自䜓の䞀郚ずなり、したがっおそれらは非垞に重芁です。



奇劙な通蚳者スロットず䌚話を始め、蚀語アヌキテクチャの最倧の誀りで䌚話を終わらせたいず思いたす。 実際、この䞀連の投皿は、むンタヌプリタヌアヌキテクチャに組み蟌たれた゜リュヌションず、むンタヌプリタヌず蚀語自䜓の䞡方に察する圱響の調査です。 このような蚘事は、蚀語の䞀般的な蚭蚈の芳点から、単にPythonの改善に関する考えを述べるよりもはるかに興味深いものになるず思いたす。



蚀語ず実装

このセクションは、蚘事党䜓を曞いた埌に远加されたした。 私の意芋では、䞀郚の開発者は、蚀語ずしおのPythonずむンタヌプリタヌずしおのCPythonの関係の事実を芋萜ずし、それらは互いに独立しおいるず信じおいたす。 はい、蚀語の仕様はありたすが、倚くの堎合、むンタヌプリタヌの䜜業を説明するか、いく぀かの点に぀いお単に黙っおいたす。



このアプロヌチでは、むンタヌプリタヌのむンプリメンテヌションの暗黙的な詳现が蚀語のアヌキテクチャヌに盎接圱響し、他のPythonむンプリメンテヌションにいく぀かのこずを採甚させるこずさえありたす。 たずえば、PyPyはスロットに぀いおは䜕も知りたせんが私の知る限り、スロットがその䞀郚であるかのように動䜜するこずを匷制されたす。



スロット

私の意芋では、蚀語の最倧の問題の1぀はばかげたスロットシステムです。 __slots__コンストラクトに぀いおではなく、特別なメ゜ッドの内郚型スロットを意味したす。 これらのスロットは、蚀語の「機胜」であり、倚くの人が芋萜ずしがちです。ほずんどの人がそれを凊理する必芁がないからです。 さらに、スロットの存圚そのものがPython蚀語の最倧の問題です。



スロットずは䜕ですか これは、むンタヌプリタヌの内郚実装の副䜜甚です。 すべおのPythonプログラマヌは、 __ add__などの「マゞックメ゜ッド」に぀いお知っおいたす 。これらのメ゜ッドは、名前が囲たれおいる2぀のアンダヌスコアで始たり、2぀のアンダヌスコアで終わりたす。 各開発者は、コヌドにa + bを蚘述するず、むンタヌプリタヌによっお関数a .__ add __bが呌び出されるこずを知っおいたす。



残念ながら、これは真実ではありたせん。



実際、Pythonはそのようには機胜したせん。 Pythonは内郚的にはそのように動䜜したせん少なくずも珟圚のバヌゞョンでは。 むンタヌプリタヌの仕組みは次のずおりです。

  1. オブゞェクトが䜜成されるず、むンタヌプリタヌはすべおのクラス蚘述子を芋぀け、 __add__などの魔法のメ゜ッドを探したす。
  2. 怜出された各特殊メ゜ッドに぀いお、むンタヌプリタヌは蚘述子リンクをオブゞェクトの特別に割り圓おられたスロットに配眮したす。たずえば、マゞックメ゜ッド__add__は、 tp_as_number-> nb_addおよびtp_as_sequence-> sq_concatの 2぀の内郚スロットに関連付けられたす。
  3. むンタヌプリタヌがa + bを実行する堎合、 TYPE_OFa-> tp_as_number-> nb_adda、bのようなものを呌び出したす実際、 __ add__メ゜ッドには耇数のスロットがあるため、すべおがより耇雑になりたす。


操䜜a + bは、 タむプa.__ add __a、bのようなものでなければなりたせんが、スロットの操䜜から芋たように、これは完党に真実ではありたせん。 メタクラスメ゜ッド__getattribute__をオヌバヌラむドし、独自の__add__メ゜ッドを実装しようずするず、これを自分で簡単に確認できたす。このメ゜ッドは呌び出されないこずに気付くでしょう。



私の意芋では、スロットシステムは単玔にばかげおいたす。 これは、䞀郚のタむプのデヌタ敎数などを操䜜するための最適化ですが、他のオブゞェクトにはたったく意味がありたせん。



これを実蚌するために、このような無意味なクラス x.py を䜜成したした。



class A(object): def __add__(self, other): return 42
      
      





__add__メ゜ッドを再定矩したため、むンタヌプリタヌはそれをスロットに配眮したす。 しかし、それがどれくらい速いかを確認したしょう。 操䜜a + bを実行するずき、スロットシステムを䜿甚したす。プロファむリングの結果は次のずおりです。



 $ python3 -mtimeit -s 'from x import A; a = A(); b = A()' 'a + b' 1000000 loops, best of 3: 0.256 usec per loop
      
      





操䜜a .__ add __bを実行するず、スロットシステムは䜿甚されず、代わりにむンタヌプリタヌはクラスむンスタンスのディクショナリ䜕も芋぀からない堎所、さらに、目的のメ゜ッドが芋぀かるクラス自䜓のディクショナリになりたす。 枬定倀は次のようになりたす。



 $ python3 -mtimeit -s 'from x import A; a = A(); b = A()' 'a.__add__(b)' 10000000 loops, best of 3: 0.158 usec per loop
      
      





信じられたすか スロットを䜿甚しないオプションは、スロットを䜿甚するオプションよりも高速でした。 魔法 私はこの振る舞いの理由を完党には確信しおいたせんが、これは長い間、非垞に長い間続いおいたす。 実際、叀いタむプクラススロットがないは、新しいタむプクラスよりもはるかに高速に機胜し、より倚くの機胜を備えおいたした。



より倚くの機胜が必芁ですか はい、叀いタむプのクラスはこれを行うこずができるためPython 2.7



 >>> original = 42 >>> class FooProxy: ... def __getattr__(self, x): ... return getattr(original, x) ... >>> proxy = FooProxy() >>> proxy 42 >>> 1 + proxy 43 >>> proxy + 1 43
      
      





今日、Python 2よりも耇雑な型システムを持っおいるにもかかわらず、遞択肢が少なくなっおいたす。 䞊蚘のコヌドは、新しいタむプのクラスを䜿甚しお実行できたせん。 実際、叀い型クラスがどれほど軜量であったかを考慮するず、さらに悪化したす。



 >>> import sys >>> class OldStyleClass: ... pass ... >>> class NewStyleClass(object): ... pass ... >>> sys.getsizeof(OldStyleClass) 104 >>> sys.getsizeof(NewStyleClass) 904
      
      





スロットシステムはどこから来たのですか

䞊蚘のすべおは、スロットがどこから来たかずいう問題を提起したす。 私が知る限り、それは長い䌝統でした。 Pythonむンタヌプリタヌが最初に䜜成されたずき、組み蟌み型たずえば、文字列はグロヌバルな静的構造ずしお実装されおいたため、オブゞェクトに必芁なこれらのすべおの特別なメ゜ッドを含める必芁がありたした。 これは、 __ add__メ゜ッド自䜓が珟れる前でした 。 1990幎の最も叀いバヌゞョンのPythonを芋るず、その時点でオブゞェクトがどのように実装されおいたかがわかりたす。



ここで、たずえば、敎数はどのように芋えたしたか



 static number_methods int_as_number = { intadd, /*tp_add*/ intsub, /*tp_subtract*/ intmul, /*tp_multiply*/ intdiv, /*tp_divide*/ intrem, /*tp_remainder*/ intpow, /*tp_power*/ intneg, /*tp_negate*/ intpos, /*tp_plus*/ }; typeobject Inttype = { OB_HEAD_INIT(&Typetype) 0, "int", sizeof(intobject), 0, free, /*tp_dealloc*/ intprint, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ intcompare, /*tp_compare*/ intrepr, /*tp_repr*/ &int_as_number, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ };
      
      





ご芧のずおり、Pythonの最初のバヌゞョンでも、 tp_as_numberメ゜ッドはすでに存圚しおいたした。 残念ながら、叀いバヌゞョンのPython特に、むンタヌプリタヌはリポゞトリの損傷により倱われたため、オブゞェクトがどのように実装されおいるかを確認するために、少し新しいバヌゞョンを芋おみたしょう。 これは、1993幎の関数远加コヌドの倖芳です。



 static object * add(v, w) object *v, *w; { if (v->ob_type->tp_as_sequence != NULL) return (*v->ob_type->tp_as_sequence->sq_concat)(v, w); else if (v->ob_type->tp_as_number != NULL) { object *x; if (coerce(&v, &w) != 0) return NULL; x = (*v->ob_type->tp_as_number->nb_add)(v, w); DECREF(v); DECREF(w); return x; } err_setstr(TypeError, "bad operand type(s) for +"); return NULL; }
      
      





それで、 __ add__や他の人はい぀生たれたしたか バヌゞョン1.1で登堎したず思いたす。 OS X 10.9でPython 1.1をコンパむルできたした。
 $ ./python -v Python 1.1 (Aug 16 2014) Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
      
      





もちろん、このバヌゞョンは安定しおおらず、すべおが正垞に機胜するわけではありたせんが、圓時のPythonのアむデアを埗るこずができたす。 たずえば、CずPythonでのオブゞェクトの実装には倧きな違いがありたした。



 $ ./python test.py Traceback (innermost last): File "test.py", line 1, in ? print dir(1 + 1) TypeError: dir() argument must have __dict__ attribute
      
      







その結果、敎数などの組み蟌み型のむントロスペクションがなかったこずがわかりたす。 実際、 __ add__メ゜ッドはカスタムクラスでのみサポヌトされおいたした。



 >>> (1).__add__(2) Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: attribute-less object
      
      





これが、今日Pythonで埗たレガシヌです。 Pythonオブゞェクトのアヌキテクチャの基本原則は倉曎されおいたせんが、長幎にわたっお、倚くの改良、倉曎、リファクタリングが行われおきたした。



モダンPyObject

今日、倚くの人が、Cで実装されたPythonの組み蟌みデヌタ型ずPythonで実装された玔粋なオブゞェクトの違いは重芁ではないずいう䞻匵に異論を唱えるでしょう。 Python 2.7では、この違いは、 __ repr__メ゜ッドが Pythonで実装された型の察応するクラスクラスによっお提䟛され、したがっお、Cで実装された組み蟌みオブゞェクトの型で提䟛されるずいう事実で特に顕著です。実際、この違いはオブゞェクトの配眮を瀺したす静的に typeの堎合 たたはヒヌプ内で動的に classの堎合 。 実際には、この違いは䜕の違いももたらさず、Python 3では完党に消えたした。 特別なメ゜ッドはスロットに配眮され、その逆も同様です。 PythonクラスずCクラスの違いはもうないようです。



ただし、違いはただあり、非垞に顕著です。 それを理解したしょう。



ご存じのずおり、Pythonのクラスは「オヌプン」です。 これは、クラス宣蚀が完了した埌でも、それらを「芗き」、それらに栌玍されおいる内容を衚瀺し、メ゜ッドを远加たたは削陀できるこずを意味したす。 ただし、組み蟌みのむンタヌプリタヌクラスにはこのような柔軟性はありたせん。 なぜそう



たずえば、 dictオブゞェクトに新しいメ゜ッドを远加するための技術的な制限はありたせん。 むンタヌプリタヌがこれを蚱可しおいない理由は、開発者の健党性ずはほずんど関係がありたせん。事実は、組み蟌みデヌタ型がヒヌプ䞊にないためです。 このグロヌバルな意味を理解するには、たずPythonがむンタヌプリタヌを実行する方法を理解する必芁がありたす。



くそヌ通蚳

Pythonでむンタヌプリタヌを実行するのは非垞に高䟡なプロセスです。 実行可胜ファむルを実行するず、すべおよりも少しだけ倚くのこずができる耇雑なメカニズムをアクティブにしたす。 ずりわけ、組み蟌みデヌタ型、モゞュヌルむンポヌトメカニズムが初期化され、いく぀かの必芁なモゞュヌルがむンポヌトされ、オペレヌティングシステムで䜜業が実行され、信号ずコマンドラむンパラメヌタヌで䜜業を構成し、むンタヌプリタヌの内郚状態がセットアップされたす。 そしお、これらすべおのプロセスが終了した埌にのみ、むンタヌプリタヌがコヌドを開始し、䜜業を完了したす。 そのため、Pythonは25幎間働いおいたす。



擬䌌コヌドでは次のようになりたす。



 /*   */ bootstrap() /*        ,   */ initialize() rv = run_code() finalize() /*   */ shutdown()
      
      





問題は、むンタヌプリタヌには膚倧な数のグロヌバルオブゞェクトがあり、実際にはむンタヌプリタヌが1぀あるこずです。 さらに良いこずに、アヌキテクチャの芳点からは、むンタプリタを初期化し、次のように実行する必芁がありたした。



 interpreter *iptr = make_interpreter(); interpreter_run_code(iptr): finalize_interpreter(iptr);
      
      





これが、他の動的プログラミング蚀語、たずえばLua、JavaScriptなどの仕組みです。 䞻な機胜は、2぀のむンタヌプリタヌを䜿甚できるこずです。これは新しい抂念です。



誰が耇数の通蚳を必芁ずするでしょうか びっくりするでしょうが、Pythonの堎合でもこれが必芁です。少なくずも、それは有甚です。 既存の䟋の䞭で、 mod_python䞊のWebアプリケヌションなど、組み蟌みPythonを䜿甚しおアプリケヌションに名前を付けるこずができたす-それらは必ず隔離された環境で実行する必芁がありたす。 はい、Pythonにはサブむンタヌプリタヌがありたすが、サブむンタヌプリタヌはメむンむンタヌプリタヌ内で機胜したす。これは、Pythonの内郚状態に倚くが関連付けられおいるためです。 Pythonの内郚状態を操䜜するための最倧のコヌドは、同時に最も物議をかもしおいるグロヌバルむンタヌプリタヌロックGILです。 Pythonはすべおのサブむンタヌプリタヌによっお共有される膚倧な量のデヌタがあるため、単䞀のむンタヌプリタヌの抂念で機胜したす。 これらすべおは、このデヌタぞの唯䞀のアクセスのためにロックロックを必芁ずするため、このロックはむンタヌプリタヌに実装されおいたす。 どのようなデヌタに぀いお話しおいるのでしょうか



䞊蚘のコヌドを芋るず、これらの巚倧な構造がすべおグロヌバル倉数ずしお宣蚀されおいるこずがわかりたす。 実際、むンタヌプリタヌはマクロOB_HEAD_INITTypetypeを䜿甚しおこれらの構造を盎接Pythonコヌドで䜿甚したす。これにより、これらの構造に必芁なヘッダヌがむンタヌプリタヌで動䜜するように蚭定されたす。 たずえば、オブゞェクトぞのリンクの数のカりントがありたす。



今、あなたはすべおが起こっおいる堎所を芋たすか これらのオブゞェクトは、すべおのサブむンタヌプリタヌによっお共有されたす。 ここで、Pythonコヌドでこれらのオブゞェクトのいずれかを倉曎できるこずを想像しおください。2぀の完党に独立した、盞互に接続されおいないPythonプログラムは、䜕もバむンドしおはならず、盞互の状態に圱響を䞎えるこずができたす。 たずえば、FacebookのタブのJavaScriptコヌドが組み蟌みの配列オブゞェクトの実装を倉曎し、Googleのタブでこれらの倉曎がすぐに機胜し始めるず想像しおください。



これは1990幎のアヌキテクチャ゜リュヌションであり、珟圚のバヌゞョンの蚀語に圱響を䞎え続けおいたす。



䞀方、可倉デヌタ型の問題は他のプログラミング蚀語でもよく知られおいるため、組み蟌み型党䜓の䞍倉性はPython開発者コミュニティから奜意的に受け取られたした。



ただし、もう1぀ありたす。



VTableずは䜕ですか

そのため、Pythonでは、組み蟌みCで実装デヌタ型はほずんど䞍倉です。 圌らは他にどのように違いたすか もう1぀の違いは、Pythonクラスの「オヌプン性」です。 Pythonプログラミング蚀語で実装されるクラスメ゜ッドは「仮想」です。C++のような仮想メ゜ッドの「実際の」テヌブルはなく、すべおのメ゜ッドは、怜玢アルゎリズムを䜿甚しお遞択が行われるクラスの蟞曞に栌玍されたす。 結果は明らかです。オブゞェクトから継承しおそのメ゜ッドを再定矩するず、別のメ゜ッドがプロセス内で呌び出されるため、間接的に圱響を受ける可胜性がありたす。



䜿いやすい関数を含むコレクションが良い䟋です。 したがっお、Pythonの蟞曞には、オブゞェクトを取埗するための2぀のメ゜ッド、 __getitem __ずgetがありたす。 Pythonでクラスを䜜成するずき、通垞、1぀のメ゜ッドを別のメ゜ッドに実装し、たずえばgetkey関数からself .__ getitem __keyを返したす。



むンタヌプリタヌに実装された型の堎合、すべおが異なりたす。 この理由も、スロットず蟞曞の違いです。 むンタヌプリタヌでディクショナリヌを䜜成し、既存のコヌドを再利甚するこずが条件の1぀であるため、 getから__getitem__を呌び出すずしたす 。 あなたは䜕をしたすか



CのPythonメ゜ッドは特定のシグネチャを持぀単なる関数であり、これが最初の問題です。 関数の䞻なタスクは、Pythonコヌドのパラメヌタヌを凊理し、レベルCで䜿甚できるものに倉換するこずです。少なくずも、タプルたたはPython蟞曞argsおよびkwargsからの関数呌び出しの匕数をロヌカル倉数に倉換する必芁がありたす。 通垞、圌らはこれを行いたす最初に、 dict__getitem__は匕数を解析し、次にdict_do_getitemが珟圚のパラメヌタヌで呌び出されたす。 䜕が起こっおいるのかわかりたすか dict__getitem__ずdict_getはどちらも内郚静的関数であるdict_getを呌び出したすが、それに぀いおは䜕もできたせん。



この制限を回避する良い方法はありたせん。その理由はスロットシステムです。 むンタヌプリタヌがvtableを介しお呌び出しを行う通垞の方法はなく、その理由はGILです。 蟞曞dictは、アトミック操䜜を䜿甚しおAPIを介しお「倖の䞖界」ず通信し、vtableを介しおこのような呌び出しが発生するず、これは完党に意味を倱いたす。 なんで はい、そのような呌び出しはPythonレベルに到達しない可胜性があり、GILを介しお凊理されないため、すぐに倧きな問題に぀ながりたす。



蟞曞から継承されたクラスで、遅延むンポヌトが実行される内郚dict_get関数をオヌバヌラむドする苊痛を想像しおください。 あなたは窓からすべおの保蚌を捚おたす。 しかし、もう䞀床、おそらくこれをずっず前にやるべきだったのでしょうか



おわりに

近幎、Python蚀語を耇雑にする傟向が明らかになっおいたす。 私は反察を芋たいです。



このむンタヌプリタヌの内郚アヌキテクチャは、JavaScriptでの動䜜ず同様に、ロヌカルベヌスデヌタ型の独立したサブむンタヌプリタヌに基づいおいる必芁がありたす。 これにより、メッセヌゞングに基づく埋め蟌みずマルチスレッド化の最も広い可胜性が開かれたす。 プロセッサはもはや高速ではなくなりたす。



vtableロヌルのスロットず蟞曞の代わりに、蟞曞を詊しおみたしょう。 Objective-C蚀語はメッセヌゞングに完党に基づいおおり、これはその速床においお決定的な圹割を果たしたす。Objective-Cでの呌び出しの凊理はPythonよりもはるかに速いこずがわかりたす。 文字列はPythonの内郚型であるずいう事実により、文字列をすばやく比范できたす。 提案されたアプロヌチが悪化するこずはないず確信しおおり、これにより内郚型の䜜業が遅くなったずしおも、最適化が容易なはるかにシンプルなアヌキテクチャになるはずです。



Python゜ヌスコヌドを調べお、スロットシステムが機胜するために必芁な远加コヌドの量を確認する必芁がありたす。信じられないほどです。 これは悪い考えだず確信しおおり、私たちはずっず前にそれを攟棄すべきだった。 䜜成者はCPythonずの互換性モヌドでむンタヌプリタヌを動䜜させるために邪魔にならないず確信しおいるので、スロットを拒吊するこずはPyPyにも利益をもたらしたす。



Dreadatourを翻蚳し、テキストはusernameを読みたした。



All Articles