デザインと歴史に関するFAQ
コンテンツ
- デザインと歴史に関するFAQ
- Pythonがステートメントのグループ化にインデントを使用するのはなぜですか?
- 単純な算術演算で奇妙な結果が得られるのはなぜですか?
- 浮動小数点計算がそれほど不正確なのはなぜですか?
- Python文字列が不変なのはなぜですか?
- メソッド定義と呼び出しで「self」を明示的に使用する必要があるのはなぜですか?
- 式で割り当てを使用できないのはなぜですか?
- Pythonが一部の機能にメソッドを使用するのはなぜですか(例: list.index())が、他の関数(eg len(list))?
- join()がリストまたはタプルメソッドではなく文字列メソッドであるのはなぜですか?
- 例外はどれくらい速いですか?
- Pythonにswitchまたはcaseステートメントがないのはなぜですか?
- OS固有のスレッド実装に依存する代わりに、インタープリターでスレッドをエミュレートできませんか?
- ラムダ式にステートメントを含めることができないのはなぜですか?
- Pythonをマシンコード、C、またはその他の言語にコンパイルできますか?
- Pythonはどのようにメモリを管理しますか?
- CPythonが従来のガベージコレクションスキームを使用しないのはなぜですか?
- CPythonが終了したときにすべてのメモリが解放されないのはなぜですか?
- タプルとリストのデータ型が別々になっているのはなぜですか?
- リストはCPythonでどのように実装されていますか?
- 辞書はCPythonでどのように実装されていますか?
- 辞書キーが不変でなければならないのはなぜですか?
- list.sort()がソートされたリストを返さないのはなぜですか?
- Pythonでインターフェイス仕様をどのように指定して適用しますか?
- なぜ後藤がないのですか?
- 生の文字列(r文字列)を円記号で終わらせることができないのはなぜですか?
- Pythonに属性割り当て用の「with」ステートメントがないのはなぜですか?
- if / while / def / classステートメントにコロンが必要なのはなぜですか?
- Pythonがリストとタプルの最後にコンマを許可するのはなぜですか?
Pythonがステートメントのグループ化にインデントを使用するのはなぜですか?
Guido van Rossumは、グループ化にインデントを使用することは非常にエレガントであり、平均的なPythonプログラムの明確さに大きく貢献すると考えています。 ほとんどの人はしばらくするとこの機能を愛するようになります。
開始/終了ブラケットがないため、パーサーと人間のリーダーによって認識されるグループ化の間に不一致が生じることはありません。 時折、Cプログラマーは次のようなコードの断片に遭遇するでしょう。
if (x <= y)
x++;
y--;
z++;
条件が真の場合はx++
ステートメントのみが実行されますが、インデントにより、そうでない場合は信じられます。 経験豊富なCプログラマーでさえ、x > y
でもy
がデクリメントされているのはなぜかと長い間見つめていることがあります。
開始/終了ブラケットがないため、Pythonはコーディングスタイルの競合を起こしにくいです。 Cでは、中括弧を配置するさまざまな方法があります。 あるスタイルを使用するコードの読み取りと書き込みに慣れている場合、別のスタイルを読み取る(または書き込む必要がある)ときに、少なくとも少し不安を感じるでしょう。
多くのコーディングスタイルは、それ自体で行に開始/終了ブラケットを配置します。 これにより、プログラムがかなり長くなり、貴重な画面スペースが無駄になり、プログラムの概要を把握するのが難しくなります。 理想的には、関数は1つの画面(たとえば、20〜30行)に収まる必要があります。 20行のPythonは、20行のCよりもはるかに多くの作業を実行できます。 これは、開始/終了ブラケットの欠如(宣言の欠如と高レベルのデータ型も原因)だけが原因ではありませんが、インデントベースの構文は確かに役立ちます。
単純な算術演算で奇妙な結果が得られるのはなぜですか?
次の質問を参照してください。
浮動小数点計算がそれほど不正確なのはなぜですか?
ユーザーは、次のような結果に驚かされることがよくあります。
>>> 1.2 - 1.0
0.19999999999999996
Pythonのバグだと思います。 そうではありません。 これはPythonとはほとんど関係がなく、基盤となるプラットフォームが浮動小数点数を処理する方法とはもっと関係があります。
CPythonの float タイプは、ストレージにC double
を使用します。 float オブジェクトの値は、固定精度(通常は53ビット)でバイナリ浮動小数点に格納され、PythonはC演算を使用します。これは、プロセッサのハードウェア実装に依存して、浮動小数点演算を実行します。 。 これは、浮動小数点演算に関する限り、PythonはCやJavaを含む多くの一般的な言語のように動作することを意味します。
10進表記で簡単に記述できる多くの数値は、2進浮動小数点で正確に表現することはできません。 たとえば、次のようになります。
>>> x = 1.2
x
に格納されている値は、10進値1.2
の(非常に良い)近似値ですが、正確には等しくありません。 通常のマシンでは、実際に保存される値は次のとおりです。
1.0011001100110011001100110011001100110011001100110011 (binary)
これは正確に:
1.1999999999999999555910790149937383830547332763671875 (decimal)
53ビットの一般的な精度は、Pythonフロートに10〜16桁の精度を提供します。
詳細な説明については、Pythonチュートリアルの浮動小数点演算の章を参照してください。
Python文字列が不変なのはなぜですか?
いくつかの利点があります。
1つはパフォーマンスです。文字列が不変であることがわかっているということは、作成時に文字列にスペースを割り当てることができ、ストレージ要件は固定されており、変更されないことを意味します。 これは、タプルとリストを区別する理由の1つでもあります。
もう1つの利点は、Pythonの文字列が数値として「要素」と見なされることです。 アクティビティの量によって値8が他の値に変更されることはなく、Pythonでは、アクティビティの量によって文字列「8」が他の値に変更されることはありません。
メソッド定義と呼び出しで「self」を明示的に使用する必要があるのはなぜですか?
アイデアはModula-3から借りました。 さまざまな理由から、非常に便利であることがわかりました。
まず、ローカル変数の代わりにメソッドまたはインスタンス属性を使用していることがより明白です。 self.x
またはself.meth()
を読むと、クラス定義を心から知らなくても、インスタンス変数またはメソッドが使用されていることが完全に明らかになります。 C ++では、ローカル変数宣言がないことでわかります(グローバルがまれであるか、簡単に認識できると仮定します)。ただし、Pythonでは、ローカル変数宣言がないため、クラス定義を検索する必要があります。もちろん。 一部のC ++およびJavaコーディング標準では、インスタンス属性にm_
プレフィックスを付ける必要があるため、この明示性はこれらの言語でも役立ちます。
次に、特定のクラスからメソッドを明示的に参照または呼び出す場合は、特別な構文は必要ありません。 C ++では、派生クラスでオーバーライドされる基本クラスのメソッドを使用する場合は、::
演算子を使用する必要があります。Pythonではbaseclass.methodname(self, <argument list>)
と記述できます。 これは、__init__()
メソッドの場合に特に役立ちます。一般に、派生クラスメソッドが同じ名前の基本クラスメソッドを拡張する必要があるため、何らかの方法で基本クラスメソッドを呼び出す必要がある場合に役立ちます。
最後に、インスタンス変数の場合、割り当てに関する構文上の問題を解決します。Pythonのローカル変数は(定義上!)関数本体で値が割り当てられる(そしてグローバルとして明示的に宣言されていない)変数であるため、次のことを行う必要があります。割り当てはローカル変数ではなくインスタンス変数に割り当てることを意図していることをインタプリタに伝えるための何らかの方法であり、(効率上の理由から)構文的であることが望ましいです。 C ++は宣言を介してこれを行いますが、Pythonには宣言がないため、この目的のためだけに宣言を導入しなければならないのは残念です。 明示的なself.var
を使用すると、これをうまく解決できます。 同様に、インスタンス変数を使用する場合、self.var
を記述する必要があるということは、メソッド内の非修飾名への参照がインスタンスのディレクトリを検索する必要がないことを意味します。 言い換えると、ローカル変数とインスタンス変数は2つの異なる名前空間に存在し、使用する名前空間をPythonに指示する必要があります。
式で割り当てを使用できないのはなぜですか?
Python 3.8以降、次のことが可能になります。
セイウチ演算子:= を使用した代入式は、式に変数を割り当てます。
while chunk := fp.read(200):
print(chunk)
詳細については、 PEP 572 を参照してください。
Pythonが一部の機能にメソッドを使用するのはなぜですか(例: list.index())が、他の関数(eg len(list))?
Guidoが言ったように:
(a)一部の演算では、接頭辞表記は後置よりも読みやすくなります。接頭辞(および中置!)演算は数学で長い伝統があり、ビジュアルが数学者が問題について考えるのに役立つ表記法が好きです。 x *(a + b)のような式をx * a + x * bに書き直す簡単な方法と、生のOO表記を使用して同じことを行う不器用さを比較してください。
(b)len(x)というコードを読んだとき、何かの長さを要求していることを知っています。 これは私に2つのことを教えてくれます:結果は整数であり、引数はある種のコンテナです。 それどころか、x.len()を読んだとき、xがインターフェイスを実装するか、標準のlen()を持つクラスから継承するある種のコンテナであることをすでに知っている必要があります。 マッピングを実装していないクラスにget()またはkeys()メソッドがある場合、またはファイルではないものにwrite()メソッドがある場合に、ときどき混乱が生じるのを目撃してください。
— https://mail.python.org/pipermail/python-3000/2006-November/004643.html
join()がリストまたはタプルメソッドではなく文字列メソッドであるのはなぜですか?
文字列は、文字列モジュールの関数を使用して常に利用可能であったのと同じ機能を提供するメソッドが追加されたときに、Python1.6以降の他の標準タイプのようになりました。 これらの新しい方法のほとんどは広く受け入れられていますが、一部のプログラマーに不快感を与えると思われる方法は次のとおりです。
", ".join(['1', '2', '4', '8', '16'])
結果が得られます:
"1, 2, 4, 8, 16"
この使用法に反対する2つの一般的な議論があります。
最初の行は、「文字列リテラル(文字列定数)のメソッドを使用すると本当に醜いように見えます」という行に沿って実行されます。答えはそうかもしれませんが、文字列リテラルは単なる固定値です。 文字列にバインドされた名前でメソッドを許可する場合、リテラルでメソッドを使用できないようにする論理的な理由はありません。
2番目の異議は通常、「文字列定数を使用してそのメンバーを結合するようにシーケンスに実際に指示しています」としてキャストされます。 悲しいことに、あなたはそうではありません。 何らかの理由で、 split()を文字列メソッドとして使用することの難しさははるかに少ないようです。その場合、それを簡単に確認できるからです。
"1, 2, 4, 8, 16".split(", ")
指定された区切り文字(または、デフォルトでは、空白の任意の実行)で区切られたサブ文字列を返す文字列リテラルへの命令です。
join()は文字列メソッドです。これを使用する場合、区切り文字列に一連の文字列を反復処理し、隣接する要素の間に挿入するように指示しているためです。 このメソッドは、自分で定義する可能性のある新しいクラスを含め、シーケンスオブジェクトのルールに従う任意の引数で使用できます。 バイトおよびバイト配列オブジェクトにも同様のメソッドが存在します。
例外はどれくらい速いですか?
例外が発生しない場合、try / exceptブロックは非常に効率的です。 実際に例外をキャッチするのは費用がかかります。 2.0より前のバージョンのPythonでは、次のイディオムを使用するのが一般的でした。
try:
value = mydict[key]
except KeyError:
mydict[key] = getvalue(key)
value = mydict[key]
これは、dictがほぼ常にキーを持っていると予想した場合にのみ意味がありました。 そうでない場合は、次のようにコーディングしました。
if key in mydict:
value = mydict[key]
else:
value = mydict[key] = getvalue(key)
この特定のケースでは、value = dict.setdefault(key, getvalue(key))
を使用することもできますが、すべてのケースで評価されるため、getvalue()
呼び出しが十分に安価である場合に限ります。
Pythonにswitchまたはcaseステートメントがないのはなぜですか?
if... elif... elif... else
のシーケンスを使用すると、これを簡単に行うことができます。 switchステートメントの構文についてはいくつかの提案がありますが、範囲テストを実行するかどうか、および実行する方法については(まだ)コンセンサスがありません。 詳細と現在のステータスについては、 PEP 275 を参照してください。
非常に多くの可能性から選択する必要がある場合は、ケース値を呼び出す関数にマッピングする辞書を作成できます。 例えば:
def function_1(...):
...
functions = {'a': function_1,
'b': function_2,
'c': self.method_1, ...}
func = functions[value]
func()
オブジェクトのメソッドを呼び出す場合は、 getattr()組み込みを使用して特定の名前のメソッドを取得することにより、さらに単純化できます。
def visit_a(self, ...):
...
...
def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()
この例では、visit_
などのプレフィックスをメソッド名に使用することをお勧めします。 このようなプレフィックスがないと、値が信頼できないソースからのものである場合、攻撃者はオブジェクトの任意のメソッドを呼び出すことができます。
OS固有のスレッド実装に依存する代わりに、インタープリターでスレッドをエミュレートできませんか?
回答1:残念ながら、インタープリターはPythonスタックフレームごとに少なくとも1つのCスタックフレームをプッシュします。 また、拡張機能はほぼランダムな瞬間にPythonにコールバックできます。 したがって、完全なスレッドの実装には、Cのスレッドサポートが必要です。
回答2:幸い、 Stackless Python があります。これは、Cスタックを回避する完全に再設計されたインタープリターループを備えています。
ラムダ式にステートメントを含めることができないのはなぜですか?
Pythonの構文フレームワークは式内にネストされたステートメントを処理できないため、Pythonラムダ式にステートメントを含めることはできません。 ただし、Pythonでは、これは深刻な問題ではありません。 機能を追加する他の言語のラムダフォームとは異なり、Pythonラムダは、関数を定義するのが面倒な場合の省略表記にすぎません。
関数はすでにPythonのファーストクラスのオブジェクトであり、ローカルスコープで宣言できます。 したがって、ローカルで定義された関数の代わりにラムダを使用する唯一の利点は、関数の名前を発明する必要がないことです。ただし、これは、関数オブジェクト(まったく同じタイプのオブジェクト)が適用されるローカル変数にすぎません。ラムダ式が生成する)が割り当てられます!
Pythonをマシンコード、C、またはその他の言語にコンパイルできますか?
Cython は、オプションのアノテーションを付けたPythonの修正バージョンをC拡張機能にコンパイルします。 Nuitka は、完全なPython言語をサポートすることを目的とした、PythonをC ++コードに変換する新進気鋭のコンパイラです。 Javaにコンパイルする場合は、 VOC を検討できます。
Pythonはどのようにメモリを管理しますか?
Pythonのメモリ管理の詳細は、実装によって異なります。 Pythonの標準実装である CPython は、参照カウントを使用してアクセスできないオブジェクトを検出し、別のメカニズムを使用して参照サイクルを収集し、アクセスできないサイクルを探して関連するオブジェクトを削除するサイクル検出アルゴリズムを定期的に実行します。 gc モジュールは、ガベージコレクションを実行し、デバッグ統計を取得し、コレクターのパラメーターを調整するための関数を提供します。
ただし、他の実装( Jython や PyPy など)は、本格的なガベージコレクターなどの別のメカニズムに依存できます。 Pythonコードが参照カウントの実装の動作に依存している場合、この違いにより、移植に関する微妙な問題が発生する可能性があります。
一部のPython実装では、次のコード(CPythonでは問題ありません)でファイル記述子が不足する可能性があります。
for file in very_long_list_of_files:
f = open(file)
c = f.read(1)
実際、CPythonの参照カウントとデストラクタスキームを使用して、 f への新しい割り当てごとに前のファイルが閉じられます。 ただし、従来のGCでは、これらのファイルオブジェクトは、さまざまな、場合によっては長い間隔でのみ収集(およびクローズ)されます。
Pythonの実装で機能するコードを記述したい場合は、ファイルを明示的に閉じるか、 with ステートメントを使用する必要があります。 これは、メモリ管理スキームに関係なく機能します。
for file in very_long_list_of_files:
with open(file) as f:
c = f.read(1)
CPythonが従来のガベージコレクションスキームを使用しないのはなぜですか?
一つには、これはCの標準機能ではないため、移植性がありません。 (はい、BoehmGCライブラリについて知っています。 ほとんどの一般的なプラットフォーム用のアセンブラコードが少し含まれていますが、すべてではありません。ほとんどが透過的ですが、完全に透過的ではありません。 Pythonを動作させるには、パッチが必要です。)
Pythonが他のアプリケーションに組み込まれている場合も、従来のGCが問題になります。 スタンドアロンのPythonでは、標準のmalloc()とfree()をGCライブラリによって提供されるバージョンに置き換えることは問題ありませんが、Pythonを埋め込むアプリケーションでは、malloc()とfree()の代わりに独自のを使用することをお勧めします。 )、Pythonは必要ないかもしれません。 現在、CPythonはmalloc()とfree()を適切に実装するものなら何でも動作します。
CPythonが終了したときにすべてのメモリが解放されないのはなぜですか?
Pythonモジュールのグローバル名前空間から参照されるオブジェクトは、Pythonの終了時に常に割り当てが解除されるとは限りません。 これは、循環参照がある場合に発生する可能性があります。 解放することが不可能なCライブラリによって割り当てられたメモリの特定のビットもあります(例: Purifyのようなツールはこれらについて文句を言うでしょう)。 ただし、Pythonは終了時にメモリをクリーンアップすることに積極的であり、すべてのオブジェクトを破棄しようとします。
Pythonに割り当て解除時に特定のものを強制的に削除させたい場合は、 atexit モジュールを使用して、それらの削除を強制する関数を実行します。
タプルとリストのデータ型が別々になっているのはなぜですか?
リストとタプルは、多くの点で類似していますが、一般的に根本的に異なる方法で使用されます。 タプルは、PascalレコードまたはC構造体に類似していると考えることができます。 それらは、グループとして操作されるさまざまなタイプの関連データの小さなコレクションです。 たとえば、デカルト座標は、2つまたは3つの数値のタプルとして適切に表されます。
一方、リストは他の言語の配列に似ています。 それらは、すべて同じタイプで、1つずつ操作されるさまざまな数のオブジェクトを保持する傾向があります。 たとえば、os.listdir('.')
は、現在のディレクトリ内のファイルを表す文字列のリストを返します。 この出力を操作する関数は、ディレクトリに別のファイルを1つか2つ追加しても、通常は機能しません。
タプルは不変です。つまり、タプルが作成されると、その要素を新しい値に置き換えることはできません。 リストは変更可能です。つまり、リストの要素はいつでも変更できます。 ディクショナリキーとして使用できるのは不変要素のみであるため、キーとして使用できるのはタプルのみであり、リストは使用できません。
リストはCPythonでどのように実装されていますか?
CPythonのリストは実際には可変長配列であり、Lispスタイルのリンクリストではありません。 実装では、他のオブジェクトへの参照の連続した配列を使用し、この配列へのポインターと配列の長さをリストヘッド構造に保持します。
これにより、リストのインデックス作成a[i]
は、コストがリストのサイズやインデックスの値に依存しない操作になります。
アイテムが追加または挿入されると、参照の配列のサイズが変更されます。 アイテムを繰り返し追加するパフォーマンスを向上させるために、ある程度の巧妙さが適用されます。 アレイを拡張する必要がある場合は、追加のスペースが割り当てられるため、次の数回は実際のサイズ変更は必要ありません。
辞書はCPythonでどのように実装されていますか?
CPythonの辞書は、サイズ変更可能なハッシュテーブルとして実装されています。 これにより、Bツリーと比較して、ほとんどの状況でルックアップ(これまでで最も一般的な操作)のパフォーマンスが向上し、実装が簡単になります。
辞書は、 hash()組み込み関数を使用して、辞書に格納されている各キーのハッシュコードを計算することによって機能します。 ハッシュコードは、キーとプロセスごとのシードによって大きく異なります。 たとえば、「Python」は-539294296にハッシュできますが、「python」は1ビット異なる文字列で1142331976にハッシュできます。 次に、ハッシュコードを使用して、値が格納される内部配列内の場所を計算します。 すべてが異なるハッシュ値を持つキーを格納していると仮定すると、これは、辞書がキーを取得するのに一定の時間(Big-O表記のO(1))を要することを意味します。
辞書キーが不変でなければならないのはなぜですか?
辞書のハッシュテーブル実装は、キー値から計算されたハッシュ値を使用してキーを検索します。 キーが可変オブジェクトの場合、その値が変更される可能性があるため、ハッシュも変更される可能性があります。 ただし、キーオブジェクトを変更した人は、それがディクショナリキーとして使用されていたことを認識できないため、ディクショナリ内のエントリを移動することはできません。 次に、辞書で同じオブジェクトを検索しようとすると、ハッシュ値が異なるため、そのオブジェクトは見つかりません。 古い値を検索しようとしても、そのハッシュビンで見つかったオブジェクトの値が異なるため、見つかりませんでした。
リストでインデックス付けされた辞書が必要な場合は、最初にリストをタプルに変換するだけです。 関数tuple(L)
は、リストL
と同じエントリを持つタプルを作成します。 タプルは不変であるため、辞書キーとして使用できます。
提案されているいくつかの容認できない解決策:
アドレス(オブジェクトID)ごとのハッシュリスト。 同じ値で新しいリストを作成すると見つからないため、これは機能しません。 例えば:
mydict = {[1, 2]: '12'} print(mydict[[../1, 2]])
2行目で使用されている
[1, 2]
のIDが最初の行のIDと異なるため、 KeyError 例外が発生します。 つまり、辞書キーは、 is ではなく、==
を使用して比較する必要があります。リストをキーとして使用する場合は、コピーを作成してください。 これは機能しません。これは、可変オブジェクトであるリストにそれ自体への参照が含まれている可能性があり、コピーコードが無限ループに陥る可能性があるためです。
リストをキーとして許可しますが、リストを変更しないようにユーザーに指示します。 これにより、誤ってリストを忘れたり変更したりしたときに、プログラム内の追跡が難しいバグのクラスが可能になります。 また、辞書の重要な不変条件を無効にします。
d.keys()
のすべての値を辞書のキーとして使用できます。リストが辞書キーとして使用されると、リストを読み取り専用としてマークします。 問題は、その値を変更できるのはトップレベルのオブジェクトだけではないということです。 リストを含むタプルをキーとして使用できます。 何かをキーとして辞書に入力するには、そこから到達可能なすべてのオブジェクトを読み取り専用としてマークする必要があります。また、自己参照オブジェクトは無限ループを引き起こす可能性があります。
必要に応じてこれを回避するコツがありますが、自己責任で使用してください。__eq__()
メソッドと__hash__()
メソッドの両方を持つクラスインスタンス内に可変構造をラップできます。 。 次に、ディクショナリ(または他のハッシュベースの構造)に存在するそのようなすべてのラッパーオブジェクトのハッシュ値が、オブジェクトがディクショナリ(または他の構造)にある間は固定されたままであることを確認する必要があります。
class ListWrapper:
def __init__(self, the_list):
self.the_list = the_list
def __eq__(self, other):
return self.the_list == other.the_list
def __hash__(self):
l = self.the_list
result = 98767 - len(l)*555
for i, el in enumerate(l):
try:
result = result + (hash(el) % 9999999) * 1001 + i
except Exception:
result = (result % 7777777) + i * 333
return result
ハッシュ計算は、リストの一部のメンバーがハッシュできない可能性と、算術オーバーフローの可能性によって複雑になることに注意してください。
さらに、o1 == o2
(つまり、o1.__eq__(o2) is True
)の場合、オブジェクトが辞書かどうか。 これらの制限を満たさない場合、辞書やその他のハッシュベースの構造は誤動作します。
ListWrapperの場合、ラッパーオブジェクトがディクショナリにあるときはいつでも、異常を回避するためにラップされたリストを変更してはなりません。 要件とそれらを正しく満たさなかった場合の結果について真剣に考える準備ができていない限り、これを行わないでください。 警告されていると考えてください。
list.sort()がソートされたリストを返さないのはなぜですか?
パフォーマンスが重要な状況では、リストを並べ替えるためだけにリストのコピーを作成するのは無駄です。 したがって、 list.sort()はリストを所定の場所に並べ替えます。 その事実を思い出させるために、ソートされたリストは返されません。 このようにして、ソートされたコピーが必要なときに誤ってリストを上書きすることに騙されることはありませんが、ソートされていないバージョンを保持する必要もあります。
新しいリストを返したい場合は、代わりに組み込みの sorted()関数を使用してください。 この関数は、提供されたiterableから新しいリストを作成し、それをソートして返します。 たとえば、辞書のキーを並べ替えられた順序で繰り返す方法は次のとおりです。
for key in sorted(mydict):
... # do whatever with mydict[key]...
Pythonでインターフェイス仕様をどのように指定して適用しますか?
C ++やJavaなどの言語で提供されるモジュールのインターフェイス仕様では、モジュールのメソッドと関数のプロトタイプが説明されています。 多くの人は、インターフェース仕様のコンパイル時の強制が大規模なプログラムの構築に役立つと感じています。
Python 2.6には、抽象基本クラス(ABC)を定義できる abc モジュールが追加されています。 次に、 isinstance()および issubclass()を使用して、インスタンスまたはクラスが特定のABCを実装しているかどうかを確認できます。 collections.abc モジュールは、 Iterable 、 Container 、 MutableMapping などの便利なABCのセットを定義します。
Pythonの場合、インターフェース仕様の利点の多くは、コンポーネントの適切なテスト規律によって得られます。
モジュールの優れたテストスイートは、回帰テストを提供し、モジュールインターフェイスの仕様および一連の例として機能します。 多くのPythonモジュールをスクリプトとして実行して、簡単な「セルフテスト」を提供できます。 複雑な外部インターフェイスを使用するモジュールでさえ、外部インターフェイスの簡単な「スタブ」エミュレーションを使用して、単独でテストできることがよくあります。 doctest および unittest モジュールまたはサードパーティのテストフレームワークを使用して、モジュール内のコードのすべての行を実行する徹底的なテストスイートを構築できます。
適切なテスト規律は、Pythonで大規模で複雑なアプリケーションを構築するのに役立つだけでなく、インターフェイスの仕様を設定するのにも役立ちます。 実際、インターフェイスの仕様ではプログラムの特定のプロパティをテストできないため、より良い場合があります。 たとえば、append()
メソッドは、いくつかの内部リストの最後に新しい要素を追加することが期待されています。 インターフェイス仕様では、append()
実装が実際にこれを正しく実行するかどうかをテストできませんが、テストスイートでこのプロパティを確認するのは簡単です。
テストスイートを作成することは非常に役立ちます。コードを簡単にテストできるように設計することをお勧めします。 ますます人気が高まっている手法の1つであるテスト指向開発では、実際のコードを作成する前に、まずテストスイートの一部を作成する必要があります。 もちろん、Pythonを使用すると、テストケースをまったく作成せずに、だらしなくすることができます。
なぜ後藤がないのですか?
1970年代に、人々は、無制限のgotoが、理解や修正が難しい厄介な「スパゲッティ」コードにつながる可能性があることに気づきました。 高水準言語では、分岐する方法がある限り、これも不要です(Pythonでは、if
ステートメントとor
、and
、およびif-else
式)およびループ(while
およびfor
ステートメントを使用し、continue
およびbreak
を含む可能性があります)。
例外を使用して、関数呼び出し間でも機能する「構造化goto」を提供することもできます。 多くの人は、例外がC、Fortran、およびその他の言語の「go」または「goto」構造のすべての合理的な使用を便利にエミュレートできると感じています。 例えば:
class label(Exception): pass # declare a label
try:
...
if condition: raise label() # goto label
...
except label: # where to goto
pass
...
これではループの途中にジャンプすることはできませんが、とにかく通常はgotoの乱用と見なされます。 慎重に使用してください。
生の文字列(r文字列)を円記号で終わらせることができないのはなぜですか?
より正確には、奇数の円記号で終了することはできません。最後の対になっていない円記号は、閉じ引用符をエスケープして、終了していない文字列を残します。
生の文字列は、独自のバックスラッシュエスケープ処理を実行するプロセッサ(主に正規表現エンジン)の入力を簡単に作成できるように設計されています。 このようなプロセッサは、一致しない末尾の円記号をとにかくエラーと見なすため、生の文字列ではそれを許可しません。 その見返りとして、バックスラッシュでエスケープすることにより、文字列の引用文字を渡すことができます。 これらのルールは、r文字列が意図された目的で使用される場合にうまく機能します。
Windowsパス名を作成しようとしている場合は、すべてのWindowsシステムコールがスラッシュも受け入れることに注意してください。
f = open("/mydir/file.txt") # works fine!
DOSコマンドのパス名を作成しようとしている場合は、たとえば の一つ
dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"
Pythonに属性割り当て用の「with」ステートメントがないのはなぜですか?
Pythonには、ブロックの実行をラップする「with」ステートメントがあり、ブロックの入口と出口でコードを呼び出します。 一部の言語には、次のような構造があります。
with obj:
a = 1 # equivalent to obj.a = 1
total = total + 1 # obj.total = obj.total + 1
Pythonでは、このような構成はあいまいになります。
Object Pascal、Delphi、C ++などの他の言語は静的型を使用するため、どのメンバーが割り当てられているかを明確に知ることができます。 これが静的型付けの要点です。コンパイラーは常にがコンパイル時にすべての変数のスコープを知っています。
Pythonは動的型を使用します。 実行時にどの属性が参照されるかを事前に知ることは不可能です。 メンバー属性は、その場でオブジェクトに追加またはオブジェクトから削除できます。 これにより、単純な読み取りから、参照されている属性(ローカル属性、グローバル属性、またはメンバー属性)を知ることができなくなります。
たとえば、次の不完全なスニペットを取り上げます。
def foo(a):
with a:
print(x)
スニペットは、「a」に「x」というメンバー属性が必要であることを前提としています。 ただし、Pythonには、インタプリタにこれを伝えるものはありません。 「a」が整数の場合はどうなるでしょうか。 「x」という名前のグローバル変数がある場合、それはwithブロック内で使用されますか? ご覧のとおり、Pythonの動的な性質により、このような選択ははるかに困難になります。
ただし、「with」および同様の言語機能(コード量の削減)の主な利点は、Pythonで割り当てによって簡単に実現できます。 それ以外の:
function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63
これを書く:
ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63
これには、Pythonで実行時に名前バインディングが解決され、2番目のバージョンでは解決を1回だけ実行する必要があるため、実行速度が向上するという副作用もあります。
if / while / def / classステートメントにコロンが必要なのはなぜですか?
コロンは、主に読みやすさを向上させるために必要です(実験的なABC言語の結果の1つ)。 このことを考慮:
if a == b
print(a)
対
if a == b:
print(a)
2番目のものが少し読みやすいことに注意してください。 このFAQの回答では、コロンが例をどのように引き立たせているかにさらに注目してください。 これは英語の標準的な使用法です。
もう1つの小さな理由は、コロンを使用すると、構文が強調表示されている編集者にとって簡単になることです。 プログラムテキストのより複雑な解析を行う代わりに、コロンを探してインデントを増やす必要がある時期を決定できます。
Pythonがリストとタプルの最後にコンマを許可するのはなぜですか?
Pythonでは、リスト、タプル、および辞書の最後に末尾のコンマを追加できます。
[1, 2, 3,]
('a', 'b', 'c',)
d = {
"A": [1, 5],
"B": [6, 7], # last trailing comma is optional but good style
}
これを許可する理由はいくつかあります。
リスト、タプル、またはディクショナリのリテラル値が複数行にまたがっている場合、前の行にコンマを追加することを覚えておく必要がないため、要素を追加する方が簡単です。 構文エラーを作成せずに行を並べ替えることもできます。
誤ってコンマを省略すると、診断が難しいエラーが発生する可能性があります。 例えば:
x = [
"fee",
"fie"
"foo",
"fum"
]
このリストには4つの要素があるように見えますが、実際には「fee」、「fiefoo」、「fum」の3つが含まれています。 常にコンマを追加すると、このエラーの原因を回避できます。
末尾のコンマを許可すると、プログラムによるコード生成も簡単になる場合があります。