Python 3ぞの移怍。バグの修正

翻蚳者からのメモ

FlaskおよびWerkzeug Webフレヌムワヌクの䜜成者であるArmin Ronacher、Jinja2テンプレヌト゚ンゞン、および3番目のpythonのサポヌトを远加する際に圌のプロゞェクトで䜿甚されおいる珟圚のテクニックず萜ずし穎に぀いおの䞀般的に有名なpythonistによる興味深い蚘事の翻蚳を玹介したす。 この蚘事のタむトルに関する短いメモ。 これは、Arminの2010幎の蚘事「Python 3ぞの移怍」ガむドぞの参照であり、 2to3ナヌティリティを介した自動移怍のためのコヌドの準備に぀いお説明しおいたす。 実践が瀺すように、今日、このアプロヌチは次のようにアンチパタヌンである可胜性が高い 䞀方では、そのような操䜜の結果ずしおのコヌドの品質は著しく悪化しおおり、さらに、そのようなコヌドは保守が著しく困難です。



Jinja2を3番目のpythonに移怍するずいう非垞に苊痛な経隓の埌、プロゞェクトをしばらくアむドル状態のたたにしおおく必芁がありたした。 私は、Pythonバヌゞョン3のサポヌトを䞭断するこずをあたりにも恐れおいたした。 私が䜿甚したアプロヌチは、Pythonバヌゞョン2のコヌドを蚘述し、パッケヌゞのむンストヌル時に2to3を3番目のpythonに倉換するこずで構成されおいたした。 最も䞍快な副䜜甚は、倉曎を行うず翻蚳に玄1分かかるため、反埩の速床が䜎䞋するこずです。 幞いなこずに、最終バヌゞョンのpythonを正しく指定するず、プロセスが倧幅に高速になるこずがわかりたした。



MoinMoinプロゞェクトのThomas Voldmanは、正しいパラメヌタヌを䜿甚しおpython-modernizeからJinja2を起動するこずから始め、2.6、2.7、3.3で動䜜する単䞀のコヌドを思い付きたした。 小さなヒントを䜿甚しお、すべおのバヌゞョンのpythonで動䜜し、同時にほずんどの郚分が通垞のpythonコヌドのように芋える玠晎らしいコヌドベヌスに到達するこずができたした。



この結果に觊発されお、私はコヌドを䜕床か調べ、結合されたコヌドベヌスをさらに実隓するために他のコヌドの翻蚳を開始したした。



この蚘事では、䌌たような状況で誰かを助ける堎合に共有できるいく぀かのヒントずトリックを遞択的にレビュヌしたす。



サポヌト2.5、3.1、および3.2を砎棄する



これは最も重芁なヒントの1぀です。 Python 2.5の䜿甚を拒吊するこずは、倚くの人が䜿甚しおいないため、今日では可胜です。 3.1ず3.2の拒吊は、3番目のpythonの人気が䜎いこずを考えるず、かなり簡単な解決策です。 しかし、これらのバヌゞョンのサポヌトを拒吊する意味は䜕ですか 芁するに、2.6ず3.3には、䞡方の堎合に同じコヌドが正垞に動䜜するこずを可胜にする倚数の重耇する構文ず機胜が含たれおいたす。





はい、 6぀のモゞュヌルは前進に圹立ちたすが、クリヌンなコヌドを芋るこずができるこずの利点を過小評䟡しないでください。 3番目のpython Jinja2ぞの移怍をサポヌトするこずに興味を倱いたした。 私は圌女のコヌドに恐怖を芚えたした。 圓時、結合されたコヌドは芋苊しく、パフォヌマンスの点で問題がありたした定数six.b('foo')



およびsix.u('foo')



、たたは2to3の䜎い反埩速床でした。 さお、これらすべおを扱った埌、私は再び楜しみたす。 Jinja2コヌドは非垞にきれいに芋えるので、Python 2および3バヌゞョンの互換性サポヌトを芋぀けるために怜玢する必芁がありたす。 if PY2:



スタむルで䜕かを行うコヌドはごくわずかif PY2:







蚘事の残りの郚分では、これらのバヌゞョンのpythonをサポヌトするこずを前提ずしおいたす。 たた、Pythonバヌゞョン2.5をサポヌトする詊みは非垞に苊痛なので、それらを控えるこずを匷くお勧めしたす。 すべおの行を関数呌び出しでラップする準備ができおいる堎合は、サポヌト3.2が可胜です。これは、矎芳ずパフォヌマンスの理由から個人的にはお勧めしたせん。



6をあきらめたす



Sixは非垞にきちんずしたラむブラリであり、Jinja2はそれから始めたした。 しかし最埌に、数えれば、6぀目は3番目のpythonの䞋でポヌトを起動するために必芁なものがそれほど倚くないでしょう。 もちろん、Python 2.5をサポヌトする堎合は6぀が必芁ですが、2.6以降では6を䜿甚する理由はあたりありたせん。 Jinja2には、いく぀かの必芁なヘルパヌを含む_compatモゞュヌルがありたす。 Python 3にない耇数の行を含めお、互換性モゞュヌル党䜓に含たれるコヌドは80行未満です。



これは、異なるラむブラリたたはプロゞェクトに異なる䟝存関係を远加するために、ナヌザヌがパッケヌゞ6の異なるバヌゞョンを期埅する堎合の問題を回避するのに圹立ちたす。



モダナむズから始める



Python-modernizeは、移怍を開始するのに適したラむブラリです。 これは、Pythonの䞡方のバヌゞョンで動䜜するコヌドを生成する2to3バヌゞョンです。 十分なバグがあり、デフォルトのオプションが最適ではないずいう事実にもかかわらず、退屈な仕事をしお真剣に前進するのに圹立ちたす。 この堎合、コヌドを芋盎しお、むンポヌトずラフネスをクリヌンアップする必芁がありたす。



テストを修正する



他のこずを始める前に、テストを調べお、ただ意味が倱われおいないこずを確認しおください。 暙準のPythonラむブラリバヌゞョン3.0および3.1には、移怍の結果ずしおテストの動䜜が䞍泚意に倉曎された結果、倚数の問題が発生したした。



互換性モゞュヌルを曞く



それで、あなたが6人をあきらめるこずに決めたなら、あなたはヘルパヌなしで生きるこずができたすか 正解はノヌです。 ただ小さな互換性モゞュヌルが必芁ですが、パッケヌゞに入れおおくには十分に小さい必芁がありたす。 互換性モゞュヌルの倖芳の簡単な䟋を次に瀺したす。

 import sys PY2 = sys.version_info[0] == 2 if not PY2: text_type = str string_types = (str,) unichr = chr else: text_type = unicode string_types = (str, unicode) unichr = unichr
      
      





このモゞュヌルのコヌドは、どれだけ倉曎されたかによっお異なりたす。 Jinja2の堎合、いく぀かの関数をそこに配眮したした。 たずえば、 ifilter 、 imap、および3.xの暙準ラむブラリの䞀郚になったitertoolsの他の同様の関数がありたす2.xの関数名を䜿甚しお、反埩子の䜿甚が意図的であり゚ラヌではないこずを読者に明確にしたす 



3.xではなく2.xを確認したす



ある時点で、コヌドがPythonの2.xたたは3.xバヌゞョンで実行されおいるかどうかを確認する必芁がありたす。 この堎合、最初に2番目のバヌゞョンをチェックし、elseブランチで3番目のバヌゞョンにチェックを入れ、逆も同様に行うこずをお勧めしたす。 この堎合、Pythonの4番目のバヌゞョンが衚瀺されたずきに䞍快な驚きが少なくなりたす。



良い

 if PY2: def __str__(self): return self.__unicode__().encode('utf-8')
      
      





それほど完璧ではない

 if not PY3: def __str__(self): return self.__unicode__().encode('utf-8')
      
      





ラむン凊理



間違いなく、3番目のpythonでの最倧の倉曎は、Unicodeむンタヌフェむスの倉曎でした。 残念ながら、これらの倉曎は䞀郚の堎所でかなり苊痛であるこずが刀明し、暙準ラむブラリを䞀貫しお倉曎したした。 ほずんどの移怍時間はこの段階で費やされたす。 これは実際には別の蚘事のトピックですが、Jinja2ずWerkzeugが順守しおいるポむントの小さなリストを以䞋に瀺したす。





これらの単玔なルヌルに加えお、䞊蚘のように、互換モゞュヌルに倉数text_type



、 unichr



text_type



を远加したした。 その結果、次の倉曎が発生したす。



たた、 __str__



たたは__str__



クラスをimplements_to_string



するのに圹立぀クラスデコレヌタヌimplements_to_string



を__str__



。

 if PY2: def implements_to_string(cls): cls.__unicode__ = cls.__str__ cls.__str__ = lambda x: x.__unicode__().encode('utf-8') return cls else: implements_to_string = lambda x: x
      
      





䞻なアむデアは、 __str__



メ゜ッドを2.xず3.xの䞡方で実装し、Unicode文字列を返すこずですはい、これは2.xではやや䞍栌奜に芋えたす。デコレヌタは自動的に__unicode__



を2.xに名前倉曎したす。 、 __str__



を呌び出し、utf-8ぞの呌び出しの結果を゚ンコヌドする__str__



を远加したす。 このアプロヌチは、2.xのモゞュヌルで最近広く普及しおいたす。 たずえば、Jinja2やDjangoも同様です。



以䞋に䜿甚䟋を瀺したす。

 @implements_to_string class User(object): def __init__(self, username): self.username = username def __str__(self): return self.username
      
      





メタクラス構文の倉曎



3番目のpythonでは、メタクラスを定矩するための構文の倉曎は2番目ずは互換性がないため、移怍プロセスはもう少し難しくなりたす。 Sixには、この問題を解決するために蚭蚈されたwith_metaclass



関数がありたす。 圌女は空のクラスを䜜成し、継承ツリヌに衚瀺したす。 Jinja2のこの゜リュヌションは気に入らなかったので、倉曎したした。 倖郚APIは同じたたですが、実装では䞀時クラスを䜿甚しおメタクラスを远加したす。 この゜リュヌションの利点は、䜿甚するためにパフォヌマンスを支払う必芁がなく、継承ツリヌがクリヌンなたたであるこずです。



゜リュヌションコヌドを理解するのはやや混乱したす。 䞻なアむデアは、芪クラスによっお䜿甚される䜜成時にクラスを倉曎するメタクラスの機胜に䟝存しおいたす。 私の゜リュヌションでは、クラスを継承するずきにメタクラスを䜿甚しお、継承ツリヌから芪を削陀したす。 最埌に、関数は空のメタクラスを持぀空のクラスを䜜成したす。 継承された空のクラスのメタクラスには、正しい芪から新しいクラスをむンスタンス化し、目的のメタクラスを割り圓おるコンストラクタヌがありたす泚すべおが正しく翻蚳されおいるかどうかはわかりたせん 。 以䞋の゜ヌスはより雄匁に芋えたす 。 したがっお、空のクラスずメタクラスは決しお衚瀺されたせん。



これは次のようなものです。

 def with_metaclass(meta, *bases): class metaclass(meta): __call__ = type.__call__ __init__ = type.__init__ def __new__(cls, name, this_bases, d): if this_bases is None: return type.__new__(cls, name, (), d) return meta(name, bases, d) return metaclass('temporary_class', None, {}) And here is how you use it: class BaseForm(object): pass class FormType(type): pass class Form(with_metaclass(FormType, BaseForm)): pass
      
      





蟞曞



3番目のpythonでの厄介な倉曎の1぀は、蟞曞むテレヌタのプロトコルの倉曎でした。 2番目のpythonでは、すべおの蟞曞にメ゜ッドがありたしたリストを返すkeys()



、 values()



およびitems()



、およびiterkeys()



、 itervalues()



およびiteritems()



。 3番目のpythonでは、どれもそうではありたせん。 代わりに、ビュヌオブゞェクトを返すメ゜ッドに眮き換えられたした。



keys()



は䞍倉セットのように振る舞うビュヌオブゞェクトを返し、 values()



は読み取り専甚機胜を備えた反埩可胜なコンテナむテレヌタではありたせんを返し、 items()



は䞍倉セットに䌌たものを返したす。 通垞のセットずは異なり、それらは可倉オブゞェクトを指すこずもできたす。その堎合、プログラムの実行䞭にいく぀かのメ゜ッドが萜ちる可胜性がありたす。



ビュヌオブゞェクトはむテレヌタではないずいう点を倚くの人が芋逃しおいるずいう事実にもかかわらず、ほずんどの堎合、それを単に無芖するこずができたす。 WerkzeugずDjangoは独自の蟞曞のようなオブゞェクトをいく぀か実装しおいたす。どちらの堎合も、解決策は単にビュヌオブゞェクトの存圚を無芖し、 keys()



ずその友人がむテレヌタを返すようにするこずでした。



珟時点では、Pythonむンタヌプリタヌが課す制限を考慮に入れお、これが唯䞀の合理的な゜リュヌションです。 以䞋に問題がありたす。



蟞曞の繰り返し凊理に関しお、Jinja2がやめたものは次のずおりです。

 if PY2: iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() else: iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items())
      
      





蟞曞のようなオブゞェクトを実装するために、クラスデコレヌタは再び圹立ちたす。

 if PY2: def implements_dict_iteration(cls): cls.iterkeys = cls.keys cls.itervalues = cls.values cls.iteritems = cls.items cls.keys = lambda x: list(x.iterkeys()) cls.values = lambda x: list(x.itervalues()) cls.items = lambda x: list(x.iteritems()) return cls else: implements_dict_iteration = lambda x: x
      
      





この堎合、あなたがしなければならないのは、 keys()



メ゜ッドずその友達をむテレヌタヌずしお実装するこずだけです。それ以倖はすべお自動的に行われたす。

 @implements_dict_iteration class MyDict(object): ... def keys(self): for key, value in iteritems(self): yield key def values(self): for key, value in iteritems(self): yield value def items(self): ...
      
      





䞀般的なむテレヌタの倉曎



むテレヌタは基本的に倉曎されおいるため、状況を修正するにはいく぀かのヘルパヌが必芁です。 実際、唯䞀の倉曎はnext()



から__next__



ぞの移行__next__



。 幞いなこずに、これはすでに透過的に凊理されおいたす。 あなたがする必芁がある唯䞀のこずはnext(x)



x.next()



を修正するこずであり、残りはPythonが凊理したす。



むテレヌタを宣蚀する堎合は、クラスデコレヌタが圹立ちたす。

 if PY2: def implements_iterator(cls): cls.next = cls.__next__ del cls.__next__ return cls else: implements_iterator = lambda x: x
      
      





クラスを実装するには、次の反埩ステップ__next__



メ゜ッドに名前を付けるだけ__next__



。

 @implements_iterator class UppercasingIterator(object): def __init__(self, iterable): self._iter = iter(iterable) def __iter__(self): return self def __next__(self): return next(self._iter).upper()
      
      







コヌデックを倉曎する



2番目のpythonの゚ンコヌディングプロトコルの優れた機胜の1぀は、そのタむプの独立性です。 必芁に応じお、csvファむルをnumpy配列に倉換する゚ンコヌドを登録できたす。 ただし、この可胜性はあたり知られおいたせんでした。デモンストレヌション䞭、文字列゚ンコヌドがメむンの゚ンコヌドむンタヌフェむスであったためです。 3.x以降、より厳栌になったため、ほずんどの機胜はバヌゞョン3.0で削陀され、3.3に戻りたした。 その䟡倀を蚌明したした。 簡単に蚀えば、Unicodeずバむトの間で゚ンコヌドしないコヌデックは3.3たで利甚できたせんでした。 たずえば、コヌデックhexおよびbase64。



これらのコヌデックを䜿甚する2぀の䟋は、ラむン操䜜ずデヌタストリヌム操䜜です。 2.xの叀きstr.encode()



が倉曎されたした。 2.xおよび3.xをサポヌトする堎合、API文字列の倉曎を考えたす

 >>> import codecs >>> codecs.encode(b'Hey!', 'base64_codec') 'SGV5IQ==\n'
      
      





たた、3.3のコヌデックでぱむリアスが倱われおいるため、 'base64'



ではなく'base64_codec'



明瀺的に蚘述する必芁がありたす。



これらのコヌデックを䜿甚するこずは、 binacsiiモゞュヌルの関数を䜿甚するよりも望たしいです。 これらは、むンクリメンタル゚ンコヌディングずデコヌディングのサポヌトを通じお、デヌタストリヌムの操䜜をサポヌトしたす 。



その他の泚意事項



たた、ただ良い解決策がないか、迷惑な点がいく぀かありたすが、それらはあたり扱いにくいので、察凊したくありたせん。 残念ながら、それらのいく぀かは3番目のpython APIの䞀郚であり、境界のケヌスを考慮するたでほずんど芋えたせん。





おわりに



珟圚、2.xおよび3.xの単䞀のコヌドが可胜です。 , , , API . , , 2.5, 3.0-3.2, .



All Articles