Pythonistaのようなコード:慣用的なPython(パート2)

Kaa、Python





短い休憩の後、David Goodgerの記事「本物のPythonistのようにコードを書く:Python idiomat の翻訳の最後の部分を紹介します





最初2番目の部分へのリンク。





この記事の著者はアメリカを発見していないことを強調しますが、ほとんどのPythonistはアメリカに「特別な魔法」を見つけません。 しかし、Pythonでさまざまなコンストラクトを使用および選択する方法論は、読みやすさとPEP8イデオロギーへの近さという点で非常に詳細です。

著者の記事のいくつかの場所では、ソースコードの例はありません。 もちろん、私はそれをそのままにしておきましたが、私自身は思いつきませんでした。原則として、著者が何を念頭に置いていたかは明らかです。










リストのジェネレーター(「リスト内包表記」-「リストの畳み込み」として-注釈。翻訳)



リストジェネレーター(略して「listcomps」)は、次のパターンの構文ショートカットです。

forおよびifステートメントを使用した従来の方法:

new_list = []<br/>

for item in a_list:<br/>

if condition(item):<br/>

new_list . append(fn(item))<br/>







リストジェネレーターの場合:

new_list = [fn(item) for item in a_list<br/>

if condition(item)]<br/>







リストジェネレーターは明確で簡潔です。 リストジェネレーターでは多くのネストされたfor条件とif条件が必要になる場合がありますが、2つ、3つのループ、またはif条件のセットには、ネストされたforループを使用することをお勧めします。 Python Zenによると、より読みやすい方法を選択することをお勧めします。

たとえば、0〜9の数字シリーズの正方形のリスト:

>>> [n ** 2 for n in range ( 10 )]<br/>

[ 0, 1 , 4 , 9 , 16 , 25 , 36 , 49 , 64 , 81 ]<br/>







0〜9の奇数の正方形のリスト:

>>> [n ** 2 for n in range ( 10 ) if n % 2 ]<br/>

[ 1 , 9 , 25 , 49 , 81 ]<br/>











ジェネレータ式(1)



数値の二乗を合計して100にしましょう。

ループ内:

total = 0

for num in range ( 1 , 101 ):<br/>

total += num * num <br/>






sum関数を使用して、適切なシーケンスをすばやく組み立てることができます。

リストジェネレーターの場合:

total = sum ([num * num for num in range ( 1 , 101 )])<br/>







ジェネレーター式の場合:

total = sum (num * num for num in xrange( 1 , 101 ))<br/>







ジェネレータ式(「genexps」)はリストジェネレータと同じくらい簡単ですが、リストジェネレータは「貪欲」であり、ジェネレータ式は「遅延」です。 リストジェネレーターは、結果リスト全体を一度に計算します。 式ジェネレーターは、必要に応じて、パスごとに1つの値のみを計算します。 これは、計算されたリストが最終結果ではなく、単なる中間ステップである長いシーケンスで特に役立ちます。

この場合、関心があるのは合計金額のみです。 中間の2乗リストは必要ありません。 同じ理由でxrangeを使用します:反復ごとに値を遅延して返します。



ジェネレータ式(2)



たとえば、数十億の数の二乗を要約すると、メモリ不足に陥り、式ジェネレータにはそのような問題はありません。 しかし、これにも関わらず、時間がかかります!

total = sum (num * num<br/>

for num in xrange( 1 , 1000000000 ))<br/>







構文の違いは、リストジェネレーターは角かっこで囲まれていますが、ジェネレーター式はそうではないことです。 ジェネレータ式は、括弧で囲む必要がある場合があるため、常に使用する必要があります。

基本的なルール:





これは最近仕事で出会った例です。

(何らかの理由で、サンプルコードがありません-約transl。)

将来の契約のために、数字(文字列と整数の両方)と月コードを含む辞書が必要です。 1行のコードで取得できます。

(何らかの理由で、サンプルコードがありません-約transl。)

以下は私たちを助けるでしょう:





最近の例:

month_codes = dict ((fn(i + 1 ), code)<br/>

for i, code in enumerate ( 'FGHJKMNQUVXZ' )<br/>

for fn in ( int , str ))<br/>







month_codesの結果:

{ 1 : 'F' , 2 : 'G' , 3 : 'H' , 4 : 'J' , ... <br/>

'1' : 'F' , '2' : 'G' , '3' : 'H' , '4' : 'J' , ... }<br/>









仕分け



Pythonでのリストの並べ替えは簡単です。

a_list . sort()<br/>







(リストのソートはそれ自体で行われることに注意してください。元のリストはソートされ、ソートメソッドリストまたはそのコピーを返しません

しかし、並べ替える必要があるが、標準とは異なる順序でデータのリストがある場合(つまり、最初の列で並べ替えてから、2番目の列で並べ替えるなど)はどうでしょうか。 最初に2番目の列、次に4番目の列でソートする必要がある場合があります。



特別な機能を備えた組み込みソートリストメソッドを使用できます。

def custom_cmp (item1, item2):<br/>

return cmp ((item1[ 1 ], item1[ 3 ]),<br/>

(item2[ 1 ], item2[ 3 ]))<br/>

<br/>

a_list . sort(custom_cmp)<br/>







これは機能しますが、リストが大きいと非常に遅くなります。



DSUで並べ替え*



DSU =デコレート-ソート-アンデコレート

*注:多くの場合、DSUはそれほど必要ではありません。 別の方法の説明については、次のセクション「キーによる並べ替え」を参照してください。

特別な比較関数を作成する代わりに、通常のソートを行う補助リストを作成します。

# Decorate: <br/>

to_sort = [(item[ 1 ], item[ 3 ], item)<br/>

for item in a_list]<br/>

<br/>

# Sort: <br/>

to_sort . sort()<br/>

<br/>

# Undecorate: <br/>

a_list = [item[ - 1 ] for item in to_sort]<br/>







最初の行は、タプルを含むリストを作成します。タプルは、目的の順序のソート条件と完全なデータレコード(要素)で構成されます。

2行目は、従来のソートを高速かつ効率的に実行します。

3行目は、ソートされたリストから最後の値を取得します。

この最後の値は、データの要素全体(レコード、ブロック)であることに注意してください。 作業が行われたソート条件は破棄され、それらはもはや必要ありません。



これにより、使用されるメモリ、アルゴリズムの複雑さ、およびランタイムの妥協点が達成されます。 はるかに簡単で高速ですが、元のリストを複製する必要があります。



キーソート



Python 2.4では、ソートリストメソッドにオプションの「キー」引数が導入されました。この引数は、各リスト項目の比較キーを計算するために使用される単一の引数の関数を設定します。 例:

def my_key (item):<br/>

return (item[ 1 ], item[ 3 ])<br/>

<br/>

to_sort . sort(key = my_key)<br/>







my_key関数は、to_sortリスト項目ごとに1回呼び出されます。

必要に応じて、独自のキー関数を収集するか、1つの引数の既存の関数を使用できます。





発電機



式ジェネレーターはすでに見ました。 任意の複雑なジェネレーターを関数として開発できます。

def my_range_generator (stop):<br/>

value = 0 <br/>

while value < stop:<br/>

yield value<br/>

value += 1 <br/>

<br/>

for i in my_range_generator( 10 ):<br/>

do_something(i)<br/>







yieldキーワードは、関数をジェネレーターに変えます。 ジェネレーター関数を呼び出すと、コードを実行する代わりに、Pythonはジェネレーターオブジェクトを返します。これは、思い出すとイテレーターです。 彼には次の方法があります。 forループは、StopIteration例外がスローされるまで次の反復子メソッドを呼び出すだけです。 上記のように、StopIterationを明示的または暗黙的に呼び出して、コードの最後で抜けることができます。

特定のリストをコンパイルする必要がないため、ジェネレーターはシーケンス/イテレーター処理を単純化できます。 反復ごとに計算される値は1つだけです。



forループが実際にどのように機能するかを説明します。 Pythonはinキーワードの後のシーケンスを調べます。 リスト、タプル、辞書、セット、ユーザー定義などの単純なコンテナの場合、Pythonはそれをイテレータに変換します。 このオブジェクトが既に反復子である場合、Pythonはそれを直接使用します。

次に、Pythonは次の反復子メソッドを繰り返し呼び出し、戻り値をループカウンター(この場合はi)にバインドし、ループ本体コードを実行します。 このプロセスは、StopIteration例外がスローされるか、ループの本体でbreakステートメントが実行されるまで繰り返し繰り返されます。

forループにはelse句を含めることができます(それ以外の場合)。そのコードは、ループの終了後に実行されますが、breakステートメントの実行後には実行されません 。 この機能は非常にエレガントなソリューションを提供します。 else節は、forループで常に使用されるとは限らず、頻繁に使用されるわけではありませんが、役に立つ場合があります。 場合によっては、必要なロジックをうまく表現できます。

たとえば、ある要素に含まれる条件を確認する必要がある場合、シーケンスの要素は次のようになります。

for item in sequence:<br/>

if condition(item):<br/>

break <br/>

else :<br/>

raise Exception ( 'Condition not satisfied.' )<br/>











ジェネレーターの例



CSVファイル(またはリストのアイテム)から空の行を除外します。

def filter_rows (row_iterator):<br/>

for row in row_iterator:<br/>

if row:<br/>

yield row<br/>

<br/>

data_file = open (path, 'rb' )<br/>

irows = filter_rows(csv . reader(data_file))<br/>









テキストファイルから行を読み取る



datafile = open ( 'datafile' )<br/>

for line in datafile:<br/>

do_something(line)<br/>







他のイテレータが行うように、ファイルは次のメソッドをサポートするため、これは可能です:リスト、タプル、辞書(キー用)、

ジェネレーター。

ここで注意してください:ファイル操作のバッファリングのため、Python 2.5+を使用していない場合、.nextメソッドと.read *メソッドを混在させることはできません。





EAFP対 LBYL



許可よりも許しを求める方が簡単です。 (許可よりも許しを求める方が簡単です)

7回測定し、1つ切ります。 (跳躍する前に見てください)

EAFPが通常好まれますが、常にではありません。





EAFP try /例を除いて



例外を起こしやすいコードをtry / exceptブロックに入れてエラーをキャッチすると、すべてのオプションを提供しようとした場合よりも一般的な解決策になる可能性があります。

try :<br/>

return str (x)<br/>

except TypeError :<br/>

... <br/>







注:キャッチする必要のある例外を常に特定する必要があります。 純粋なexcept節を使用しないでください。 純粋な例外条件は、コードで発生するすべての例外をキャッチし、デバッグを非常に困難にします。



インポート



from module import * <br/>







モジュールインポート式でこの「ワイルドカード」(ワイルドカード、テンプレート)を見たことがあるでしょう。 おそらくあなたも彼女が好きです。 使用しないでください。

有名な対話の適応:

(外側のダゴバ、ジャングル、沼地、霧。)

ハッチ:モジュールのインポートから*明示的なインポートよりも優れていますか?

依田:良くないです。 より速く、より簡単に、より魅惑的に。

ルーク:しかし、明示的なインポートの方がワイルドカードよりも優れていることをどうやって知るのですか?

依田:半年後に試してコードを読みたいときを見つけます。



(念のため、元のテキストを引用します-注。翻訳。)

(ダゴバの外装、ジャングル、沼地、ミスト。)

LUKE:モジュールからのインポートは、明示的なインポートよりも優れていますか?

依田:いいえ、良くありません。 より速く、より簡単に、より魅惑的に。

LUKE:しかし、なぜ明示的なインポートの方が優れているのかをどのようにして知ることができますか

ワイルドカード形式?

依田:コードを6か月間読もうとするとき

今から。



ワイルドカードのインポート-Pythonのダークサイド。



絶対に!

モジュールのインポートから*名前空間をひどく汚染します。 ローカルネームスペースに、受信する予定のないオブジェクトがあります。 モジュールで以前に定義されたローカルの名前をオーバーライドする名前を確認できます。 これらの名前の由来を正確に把握することはできません。 この形式は短くシンプルですが、最終的なコードには属していません。

道徳: ワイルドカードでインポートを使用しないでください!

はるかに良い:



名前空間汚染アラーム!

代わりに、

モジュールを介して名前をバインドします(詳細を説明する識別子、起源を示します):

import module <br/>

module . name<br/>







または、エイリアスを使用して長いモジュール名をインポートします。

import long_module_name as mod <br/>

mod . name<br/>







または、必要な名前のみを明示的にインポートします。

from module import name<br/>

name<br/>







このフォームは、モジュールの編集と再読み込み( "reload()")が必要な対話型インタープリターでの使用には適していません。



モジュールとスクリプト



インポートされたモジュールと実行可能スクリプトの両方を作成するには:

if __name__ == '__main__' :<br/>

# script code here <br/>







インポートすると、__ name__モジュール属性が拡張子「.py」なしのファイル名として設定されます。 したがって、モジュールがインポートされると、if条件の下のコードは機能しません。 スクリプトを実行すると、__ name__属性が "__main__"に設定され、スクリプトコード機能します。

いくつかの特別な場合を除き、すべてのコードを最上位に置くべきではありません。 関数、クラス、メソッドのコードを非表示にし、__ name__ == '__main__'の場合はコードを閉じます。





モジュール構造



"""module docstring""" <br/>

<br/>

# imports <br/>

# constants <br/>

# exception classes <br/>

# interface functions <br/>

# classes <br/>

# internal functions & classes <br/>

<br/>

def main ( ... ):<br/>

... <br/>

<br/>

if __name__ == '__main__' :<br/>

status = main()<br/>

sys . exit(status)<br/>







これがモジュールの構造です。



コマンドライン処理



例:cmdline.py:

#!/usr/bin/env python <br/>

<br/>

"""<br/>

Module docstring.<br/>

"""
<br/>

<br/>

import sys <br/>

import optparse <br/>

<br/>

def process_command_line (argv):<br/>

"""<br/>

Return a 2-tuple: (settings object, args list).<br/>

`argv` is a list of arguments, or `None` for ``sys.argv[1:]``.<br/>

"""
<br/>

if argv is None :<br/>

argv = sys . argv[ 1 :]<br/>

<br/>

# initialize the parser object: <br/>

parser = optparse . OptionParser(<br/>

formatter = optparse . TitledHelpFormatter(width = 78 ),<br/>

add_help_option = None )<br/>

<br/>

# define options here: <br/>

parser . add_option( # customized description; put --help last <br/>

'-h' , '--help' , action = 'help' ,<br/>

help = 'Show this help message and exit.' )<br/>

<br/>

settings, args = parser . parse_args(argv)<br/>

<br/>

# check number of arguments, verify values, etc.: <br/>

if args:<br/>

parser . error( 'program takes no command-line arguments; ' <br/>

'"%s" ignored.' % (args,))<br/>

<br/>

# further process settings & args if necessary <br/>

<br/>

return settings, args<br/>

<br/>

def main (argv = None ):<br/>

settings, args = process_command_line(argv)<br/>

# application code here, like: <br/>

# run(settings, args) <br/>

return 0 # success <br/>

<br/>

if __name__ == '__main__' :<br/>

status = main()<br/>

sys . exit(status)<br/>











パッケージ



package / <br/>

__init__ . py<br/>

module1 . py<br/>

subpackage / <br/>

__init__ . py<br/>

module2 . py<br/>









例:

import package.module1 <br/>

from package.subpackage import module2<br/>

from package.subpackage.module2 import name<br/>







Python 2.5では、将来のインポートを通じて絶対および相対インポートが可能になりました。

from __future__ import absolute_import<br/>







まだ十分に理解できていないので、この部分の説明は省略します。





単純なものは複雑なものよりも優れている



まず、デバッグはコードを書くよりも2倍難しくなります。 したがって、可能な限りスマートにコードを記述した場合、定義上、デバッグするのに十分なほどスマートではありません。

—ブライアンW.カーニハン、 Cプログラミング言語と「AWK」の「K」の共著者



言い換えれば、プログラムをシンプルにしてください!



車輪を再発明しないでください



コードを書く前に:





参照資料
















この記事はHabra Editorで作成され、コードサンプルはデフォルトスタイルのHabra-colorerでペイントされています。




All Articles