まえがき
Mark LutzのLearning Python本を読んだ後、私はPythonに本当に夢中になりました。 言語はとても美しく、自分の考えを書いて表現するのは楽しいです。 多数のインタープリターとコンパイラー、拡張機能、モジュール、およびフレームワークは、コミュニティーが非常に活発であり、言語が開発中であることを示しています。 言語を学ぶ過程で、私は慎重にグーグルで調べた多くの質問があり、理解できないすべての構造を理解しようとしました。 これについてはこの記事で説明しますが、この記事は初心者のPython開発者を対象としています。
用語について少し
おそらく、初心者のPythonプログラマーをしばしば混乱させる用語から始めます。
リスト内包表記またはリストジェネレーターはリストを返します。 リストジェネレーターと式ジェネレーターは常に混同しました(ただし、式ジェネレーターは!)。 同意します。ロシア語では非常に似ています。 式-ジェネレーターはジェネレーター式 、リストではなくイテレーターを返す特別な式です。 比較してみましょう:
f = (x for x in xrange(100)) # - c = [x for x in xrange(100)] #
これらは2つのまったく異なる設計です。 最初はジェネレーター(つまり、イテレーター)を返し、2番目は通常のリストです。
ジェネレータまたはジェネレータは、イテレータを返す特別な関数です。 ジェネレーターを取得するには、yieldを介して関数値を返す必要があります。
def prime(lst): for i in lst: if i % 2 == 0: yield i >>> f = prime([1,2,3,4,5,6,7]) >>> list(f) [2, 4, 6] >>> next(f) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration >>>
ところで、Python 3.3は、constructから新しいyieldを導入しました。 yieldとforの共有は頻繁に使用されるため、2つの構成要素を組み合わせることにします。
def generator_range(first, last): for i in xrange(first, last): yield i def generator_range(first, last): yield from range(first, last)
コンテキストマネージャーとは何ですか?
コンテキストマネージャは、withステートメントで囲まれたコードのブロックである特別な構造です。 withステートメントは、コンテキストマネージャープロトコルを使用してブロックを作成します。これについては、この記事の後半で説明します。 このプロトコルを使用する最も単純な関数は、open()関数です。 ファイルを開くたびに、出力をディスクにプッシュするためにファイルを閉じる必要があります(実際、Pythonはclose()メソッドを自動的に呼び出しますが、明示的に使用することをお勧めします)。 例:
fp = open("./file.txt", "w") fp.write("Hello, World") fp.close()
毎回close()メソッドを呼び出さないようにするために、open()関数のコンテキストマネージャーを使用できます。これは、ブロックを終了した後にファイルを自動的に閉じます。
with open("./file.txt", "w") as fp: fp.write("Hello, World")
ここでは、データをファイルにプッシュするたびにcloseメソッドを呼び出す必要はありません。 したがって、コンテキストマネージャを使用して、ブロックに入る前と終了した後にアクションを実行します。 しかし、コンテキストマネージャの機能はこれで終わりではありません。 多くのプログラミング言語では、このようなタスクにデストラクタを使用しています。 ただし、Pythonでは、オブジェクトが他の場所で使用される場合、オブジェクトへのすべての参照が使い果たされた場合にのみ__del__メソッドが呼び出されるため、デストラクタが呼び出される保証はありません。
In [4]: class Hello: ...: def __del__(self): ...: print 'destructor' ...: In [5]: f = Hello() In [6]: c = Hello() In [7]: e = Hello() In [8]: del e destructor In [9]: del c destructor In [10]: c = f In [11]: e = f In [12]: del f # <-
コンテキストマネージャーを使用してこの問題を解決します。
In [1]: class Hello: ...: def __del__(self): ...: print u'' ...: def __enter__(self): ...: print u' ' ...: def __exit__(self, exp_type, exp_value, traceback): ...: print u' ' ...: In [2]: f = Hello() In [3]: c = f In [4]: e = f In [5]: d = f In [6]: del d In [7]: del e In [8]: del c In [9]: del f # <-
次に、コンテキストマネージャを呼び出してみます。
In [10]: with Hello(): ....: print u' ' ....:
コードの実行後、ブロックからの終了が保証されていることがわかりました。
コンテキストマネージャープロトコル
簡単なHelloクラスを作成して、コンテキストマネージャプロトコルを簡単に確認しました。 プロトコルについて詳しく見ていきましょう。 オブジェクトがコンテキストマネージャーになるには、そのクラスに__enter__と__exit__の2つのメソッドを含める必要があります。 最初のメソッドは、ブロックに入る前に実行されます。 クラスの現在のインスタンスをメソッドに返して、asステートメントを介してアクセスできるようにすることができます。
__exit__メソッドは、withブロックの終了後に実行され、exp_type、exp_value、exp_trの3つのパラメーターが含まれています。 コンテキストマネージャは、withブロックで発生した例外をキャッチできます。 必要な例外のみをキャッチするか、不要な例外を抑制できます。
class Open(object): def __init__(self, file, flag): self.file = file self.flag = flag def __enter__(self): try: self.fp = open(self.file, self.flag) except IOError: self.fp = open(self.file, "w") return self.fp def __exit__(self, exp_type, exp_value, exp_tr): """ IOError """ if exp_type is IOError: self.fp.close() # return True self.fp.close() # with Open("asd.txt", "w") as fp: fp.write("Hello, World\n")
変数exp_typeには、発生した例外クラスが含まれます。exp_valueは例外メッセージです。 この例では、ファイルを閉じ、__ exit__メソッドにTrueを返すことでIOError例外を抑制します。 ブロック内の他のすべての例外は許可されます。 コードが終了してブロックが終了するとすぐに、発生した例外に関係なくself.fp.close()メソッドが呼び出されます。 ところで、withブロック内では、NameError、SyntaxErrorなどの例外を抑制することができますが、これを行うべきではありません。
コンテキストマネージャプロトコルは非常に使いやすいですが、一般的なタスクには、標準のPythonライブラリに付属するさらに簡単なメソッドがあります。 次に、contextlibパッケージを見てください。
Contextlibパッケージ
従来の方法、つまり__enter__および__exit__メソッドを使用してクラスを作成する方法でコンテキストマネージャーを作成することは、難しいタスクの1つではありません。 しかし、些細なコードの場合、そのようなクラスを書くには、さらに大騒ぎが必要です。 この目的のために、contextlibパッケージの一部であるcontextmanager()デコレータが発明されました。 contextmanager()デコレーターを使用して、通常の関数からコンテキストマネージャーを作成できます。
import contextlib @contextlib.contextmanager def context(): print u' ' try: yield {} except RuntimeError, err: print 'error: ', err finally: print u' '
コードの正常性を確認します。
In [8]: with context() as fp: ...: print u'' ...:
ブロック内で例外を発生させてみましょう。
In [14]: with context() as value: ....: raise RuntimeError, 'Error' ....: error: Error In [15]:
この例からわかるように、クラスを使用した実装は、contextmanager()デコレータを使用した実装と機能的に実質的に違いはありませんが、デコレータを使用するとコードが大幅に簡素化されます。
contextmanager()デコレータを使用する別の興味深い例:
import contextlib @contextlib.contextmanager def bold_text(): print '<b>' yield # with print '</b>' with bold_text(): print "Hello, World"
結果:
<b>Hello, World</b>
ルビーのブロックのように聞こえますか?
最後に、ネストされたコンテキストについて説明します。 ネストされたコンテキストを使用すると、複数のコンテキストを一度に管理できます。 例:
import contextlib @contextlib.contextmanager def context(name): print u' %s' % (name) yield name # print u' %s' % (name) with contextlib.nested(context('first'), context('second')) as (first, second): print u' %s %s' % (first, second)
結果:
最初にコンテキストを入力します
コンテキストを入力する
最初の2番目のブロック内
コンテキスト外秒
最初にコンテキストから
ネストされた関数を使用しない同様のコード:
first, second = context('first'), context('second') with first as first: with second as second: print u' %s %s' % (first, second)
このコードは前のコードと似ていますが、状況によっては希望どおりに機能しません。 コンテキスト( 'first')およびコンテキスト( 'second')オブジェクトはブロックに入る前に呼び出されるため、これらのオブジェクトで発生した例外をキャッチすることはできません。 同意して、最初のオプションははるかにコンパクトで、見た目もきれいです。 しかし、Python 2.7および3.1では、ネストされた関数は廃止され、ネストされたコンテキストの新しい構文構成が追加されました。
with context('first') as first, context('second') as second: print u' %s %s' % (first, second)
Python 2.7およびPython 3のrangeおよびxrange
Python 2.7の範囲はリストを返すことが知られています。 大量のデータをメモリに保存することは実用的ではないことに誰もが同意すると思うので、リストとほとんど同じように動作するが、すべての出力要素をメモリに保存しないxrangeオブジェクトを返すxrange関数を使用します。 しかし、大きな値が関数に渡されたときのPython 2.xのxrangeの動作には少し驚いていました。 例を見てみましょう:
>>> f = xrange(1000000000000000000000) Traceback (most recent call last): File "<stdin>", line 1, in <module> OverflowError: Python int too large to convert to C long >>>
Pythonは、intが長すぎて、C longに変換できないことを示しています。 Python 2.xには整数に関する制限があることがわかりました。sys.maxsize定数を調べることでこれを確認できます。
>>> import sys >>> sys.maxsize 9223372036854775807 >>>
これは整数の最大値です:
>>> import sys >>> sys.maxsize+1 9223372036854775808L >>>
Pythonは数値をきれいにlong intに変換しました。 Python 2.xのxrangeが大きな値に対して異なる振る舞いをしても驚かないでください。
Python 3.3では、整数は無限に大きくなる可能性があります。チェックしてみましょう。
>>> import sys >>> sys.maxsize 9223372036854775807 >>> range(sys.maxsize+1) range(0, 9223372036854775808) >>>
long intへの変換は行われませんでした。 別の例を次に示します。
>>> import sys >>> sys.maxsize + 1 9223372036854775808 >>> f = sys.maxsize + 1 >>> type(f) <class 'int'> >>>
Python 2.7で
>>> import sys >>> type(sys.maxsize + 1) <type 'long'> >>>
いくつかのデザインの明らかでない動作
pythonのシンプルさは、その学習の容易さではなく、言語自体のシンプルさにあると誰もが同意すると思います。 Pythonは美しく、柔軟性があり、オブジェクト指向のスタイルだけでなく、機能的なスタイルでも記述できます。 しかし、一見奇妙に見える一部の構造の動作について知る必要があります。 最初に、最初の例を検討します。
>>> f = [[]] * 3 >>> f[0].append('a') >>> f[1].append('b') >>> f[2].append('c') >>>
この構造の結果はどうなりますか? トレーニングを受けていない開発者が結果を報告します:[['a']、[b ']、[c']]。 しかし、実際には次のようになります。
>>> print f [['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'b', 'c']] >>>
各リストで結果が重複しているのはなぜですか? 実際、乗算演算子はリスト内に同じリストへのリンクを作成します。 これは、例に少し追加することで簡単に確認できます。
>>> c = [[], [], []] >>> hex(id(c[0])), hex(id(c[1])), hex(id(c[2])) ('0x104ede7e8', '0x104ede7a0', '0x104ede908') >>> >>> hex(id(f[0])), hex(id(f[1])), hex(id(f[2])) ('0x104ede710', '0x104ede710', '0x104ede710') >>>
最初のケースではすべてが正常であり、リストへのリンクは異なります。2番目の例では、同じオブジェクトを参照します。 最初のリストの変更は、後続のリストの変更を伴うことになりますので、注意してください。
2番目の例はすでにハブで検討されましたが、記事に含めたいと思いました。 forループを実行する関数であるlambdaを見て、各関数を辞書に入れましょう:
>>> tmp = {} >>> for i in range(10): ... tmp[i] = lambda: i >>> tmp[0]() 9 >>> tmp[1]() 9 >>>
ラムダ関数内では、変数iが閉じられ、別の変数iのインスタンスがラムダブロックに作成されます-forループ内の変数iへの参照である関数です。 forループカウンターが変わるたびに、すべてのラムダ関数の値が変わるため、すべての関数で値i-1を取得します。 これは、ラムダ関数を最初のパラメーターとして明示的にデフォルト値-i変数に渡すことで簡単に修正できます。
>>> tmp = {} >>> for i in range(10): ... tmp[i] = lambda i = i: i >>> tmp[0]() 0 >>> tmp[1]() 1 >>>