プロジェクトの構造化

「構造」とは、プロジェクトがその目的をどのように最も満たしているかについての決定を意味します。 クリーンで効果的なコードを作成するために、Pythonの機能を最大限に活用する方法を検討する必要があります。 実際には、 “構造”とは、ロジックと依存関係が明確であるクリーンなコードと、ファイルとフォルダがファイルシステムにどのように編成されているかを意味します。

どの機能をどのモジュールに入れる必要がありますか? プロジェクトを通じてデータはどのように流れますか? グループ化して分離できる機能は何ですか? このような質問に答えることで、完成した製品がどのように見えるかを広義で計画し始めることができます。

このセクションでは、Pythonのモジュールとインポートシステムを詳しく見ていきます。プロジェクトの構造を強化するための中心的な要素です。 次に、コードを拡張して確実にテストできるようにするためのさまざまな方法について議論します。

リポジトリの構造

構造は重要です。

コードスタイル、APIデザイン、オートメーションが健全な開発サイクルに欠かせないのと同様に、リポジトリ構造はプロジェクト アーキテクチャ の重要な部分です。

潜在的なユーザーまたは投稿者があなたのリポジトリのページにアクセスすると、いくつかのことが表示されます。

  • プロジェクト名
  • プロジェクトの説明
  • たくさんのファイル

フォールドの下にスクロールするときだけ、ユーザーはあなたのプロジェクトのREADMEを見ることができます。

あなたのリポジトリが、投げ捨てられた大量のファイルやネストしたディレクトリで混乱している場合、あなたの美しいドキュメントを読む前に他の場所を見るかもしれません。

もちろん、最初の印象はすべてではありません。 あなたとあなたの同僚は、このリポジトリで何時間も過ごし、最終的にはすべての隅々まで親しみを感じるようになります。 そのレイアウトは重要です。

サンプルリポジトリ

要約: これは、 Kenneth Reitz が推奨するものです。

このリポジトリは GitHubで利用可能 です。

README.rst
LICENSE
setup.py
requirements.txt
sample/__init__.py
sample/core.py
sample/helpers.py
docs/conf.py
docs/index.rst
tests/test_basic.py
tests/test_advanced.py

いくつかの具体的なことを考えてみましょう。

実際のモジュール

Location ./sample/ or ./sample.py
Purpose The code of interest

あなたのモジュールパッケージはリポジトリの中心です。 遠ざけてはいけません:

./sample/

モジュールが1つのファイルのみで構成されている場合、リポジトリのルートに直接置くことができます:

./sample.py

あなたのライブラリはあいまいなsrcまたはpythonサブディレクトリに属していません。

ライセンス

Location ./LICENSE
Purpose Lawyering up.

おそらくソースコード自体を除いて、リポジトリの最も重要な部分です。 完全なライセンステキストと著作権表示がこのファイルに存在している必要があります。

プロジェクトに使用するライセンスが不明な場合は、 choosealicense.com を参照してください。

もちろん、ライセンスなしでコードを公開することも自由ですが、それによりたくさんの人があなたのコードを使用する可能性を阻んでしまうかもしれません。

Setup.py

Location ./setup.py
Purpose Package and distribution management.

あなたのモジュールパッケージがあなたのリポジトリのルートにある場合、これは明らかにルートにあるはずです。

要件ファイル

Location ./requirements.txt
Purpose Development dependencies.

pip要件ファイル は、リポジトリのルートに置く必要があります。 プロジェクトに貢献するために必要な依存関係、つまりドキュメントのテスト、ビルド、および生成を指定する必要があります。

あなたのプロジェクトに開発の依存関係がない場合や、 setup.py を介して開発環境を設定したい場合は、このファイルは不要です。

ドキュメンテーション

Location ./docs/
Purpose Package reference documentation.

これが他の場所に存在する理由はほとんどありません。

テストスイート

Location ./test_sample.py or ./tests
Purpose Package integration and unit tests.

まず始めに、小さなテストスイートが1つのファイルに存在することがよくあります:

./test_sample.py

テストスイートが成長したら、次のようにテストをディレクトリに移動できます:

tests/test_basic.py
tests/test_advanced.py

明らかに、これらのテストモジュールはパッケージ化されたモジュールをインポートしてテストする必要があります。これにはいくつかの方法があります:

  • パッケージが site-packages にインストールされることを期待してください。
  • パッケージを適切に解決するために、単純な (しかし 明示的な )パス変更を使用します。

私は後者を強く勧めます。 開発者が積極的に変化するコードベースをテストするために setup.py develop を実行するように要求するためには、コードベースのインスタンスごとに独立した環境設定が必要です。

個々のテストにインポートコンテキストを与えるには、tests/context.py ファイルを作成します:

import os
import sys
sys.path.insert(0, os.path.abspath('..'))

import sample

次に、個々のテストモジュール内で、次のようにモジュールをインポートします:

from .context import sample

これは、インストール方法に関係なく、常に期待どおりに動作します。

あなた自身のモジュール内でテストを配布すべきだと主張する人もいますが、私は同意しません。 多くの場合、ユーザーの複雑さが増します。 多くのテストスイートでは、ほとんどが追加の依存関係と実行時コンテキストが必要になります。

Makefile

Location ./Makefile
Purpose Generic management tasks.

ほとんどのプロジェクトやPocooプロジェクトを見てみると、Makefileがあることに気付くでしょう。 なぜかというと、これらのプロジェクトはC言語で書かれていません...要するに、makeはプロジェクトの一般的なタスクを定義するために非常に便利なツールであるということです。

Sample Makefile:

init:
    pip install -r requirements.txt

test:
    py.test tests

.PHONY: init test

リポジトリのルートには、他の一般的な管理スクリプト( manage.pyfabfile.py など)も属しています。

Djangoアプリケーションについて

私はDjango 1.4のリリース以来、Djangoアプリケーションの新しいトレンドに気付きました。 多くの開発者は、新しいバンドルされたアプリケーションテンプレートのためにリポジトリを構成していません。

どうやって? 彼らはいつも空で新しいリポジトリに行き、以下を実行します:

$ django-admin.py startproject samplesite

結果のリポジトリ構造は次のようになります:

README.rst
samplesite/manage.py
samplesite/samplesite/settings.py
samplesite/samplesite/wsgi.py
samplesite/samplesite/sampleapp/models.py

しかし、このようにしないでください。

反復的なパスは、ツールと開発者の両方にとって混乱を招きます。 不要なネスティングは誰にも役立ちません(モノリシックなSVNリポジトリを懐かしくしていない限り)。

正しくは以下の通りです:

$ django-admin.py startproject samplesite .

.” に注意してください。

結果の構造:

README.rst
manage.py
samplesite/settings.py
samplesite/wsgi.py
samplesite/sampleapp/models.py

コードの構造は重要です

インポートとモジュールをPythonで処理する方法のおかげで、Pythonプロジェクトを構造化するのは比較的簡単です。 これを簡単に言うと、多くの制約がなく、モデルをインポートするモジュールが把握しやすいということです。 したがって、プロジェクトのさまざまな部分とその相互作用を作成するという、純粋なアーキテクチャ上の任務が残っています。

プロジェクトの簡単な構造化は、それを貧弱にすることも容易であることを意味します。 構造の整っていないプロジェクトのいくつかの兆候は次のとおりです。

  • 複数の乱雑な循環依存関係 furn.py の中のクラスTableとChairが table.isdoneby() のような質問に答えるために workers.py からCarpenterをインポートする必要がある場合 逆にCarpenterクラスがTableとChairをインポートする必要がある場合は、 carpenter.whatdo() という質問に答えるためには、循環依存関係があります。 この場合、メソッドや関数の中でimportステートメントを使うなど、脆弱なハックに頼らざるを得ません。
  • 非表示のカップリング: 無関係なテストケースで20回のテストが破られるのは、Carpenterのコードを破るためです。変更を適応させるためには非常に慎重な手術が必要です。つまり、Carpenterのコード内のテーブルについての仮定があまりにも多いか、その逆のことです。
  • グローバルな状態やコンテキストの大量使用: 明示的に (height, width, type, wood) に渡すのではなく、TableとCarpenterは変更可能なグローバル変数に依存しており。 矩形テーブルが正方形になった理由を理解するために、これらのグローバル変数へのすべてのアクセスを精査し、リモートテンプレートコードがこのコンテキストを変更してテーブル次元を混乱させていることを発見する必要があります。
  • スパゲッティコード:入れ子にされたif節の複数のページと、多数のコピーペーストされた手続き型コードと適切なセグメンテーションのないループの場合はスパゲッティコードとして知られています。 Pythonの意味のあるインデント(最も論争の的になっている機能の1つ)は、この種のコードを維持することを非常に困難にしています。 良いニュースはあなたがあまりそれを見ないかもしれないということです。
  • ラビオリコードは、Pythonの可能性が高いです: それは、何百もの類似した小さなロジック、しばしばクラスまたはオブジェクトで構成され、適切な構造がありません。 FurnitureTable、AssetTableまたはTable、またはTableNewを使用しなければならないことを決して覚えていない場合は、ラビオリコードで泳いでいるかもしれません。

モジュール

Pythonモジュールは、利用可能な主要な抽象レイヤーの1つであり、おそらく最も自然なものです。抽象レイヤでは、コードを関連するデータと機能を保持する部分に分けることができます。

たとえば、プロジェクトのレイヤーはユーザーアクションとのインタフェースを処理でき、別のレイヤーはデータの低レベル操作を処理します。 これらの2つの層を分離する最も自然な方法は、1つのファイル内のすべてのインターフェース機能と、別のファイル内のすべての低レベル操作を再グループ化することです。 この場合、インターフェイスファイルは低レベルのファイルをインポートする必要があります。 これは importfrom ... import 文で行います。

import 文を使うとすぐにモジュールを使います。 これらのモジュールは、 ossys などの組み込みモジュール、環境にインストールしたサードパーティモジュール、プロジェクトの内部モジュールのいずれかです。

スタイルガイドと一致するように、モジュール名は小文字にしておき、ドット (.) や疑問符 (?) などの特別な記号は使用しないでください。 したがって、 my.spam.py のようなファイル名は避けてください! このように命名すると、Pythonがモジュールを探す方法が妨げられます。

my.spam.py の場合、Pythonは my という名前のフォルダにある spam.py ファイルを探します。Pythonドキュメントではドット表記をどのように使うべきかの があります。

モジュール名を my_spam.py とすることもできますが、私たちの友人でさえ、アンダースコアはモジュール名でよく見られるべきではありません。

いくつかの命名制限の他に、Pythonファイルがモジュールであるために特別なものは必要ありませんが、この概念を適切に使用し、いくつかの問題を避けるためには、インポートメカニズムを理解する必要があります。

具体的には、 import modu 文は適切なファイルを探します。これは、呼び出し側と同じディレクトリに modu.py がある場合です。見つからなければ、Pythonインタプリタは “path”内の modu.py を再帰的に検索し、見つからなければImportError例外を送出します。

一旦 modu.py が見つかると、Pythonインタプリタはモジュールを隔離したスコープで実行します。 そして、 modu.py 内のトップレベルのステートメントが実行されます。 関数とクラスの定義は、モジュールの辞書に格納されています。

次に、モジュールの変数、関数、およびクラスは、Pythonで特に有用で強力なプログラミングの中心概念である、モジュールの名前空間を通じて呼び出し元が利用できるようになります。

多くの言語では、 include file ディレクティブがプリプロセッサで使用され、ファイル内のすべてのコードを取得し、呼び出し側のコードにコピーします。しかし Pythonではこれが異なります。含まれているコードはモジュールの名前空間で分離されています。これは、一般的に、含まれているコードが望ましくない影響を及ぼすことを心配する必要がないことを意味しているため、既存の関数を同じ名前で上書きします。

import文の特殊な構文を使用すると、より標準的な動作をシミュレートすることができます: from modu import *。 これは一般に悪い習慣とみなされます。 import *使うとコードの読み込みが難しくなり、依存関係をコンパートメント化しにくくなるからです

from modu import func は、インポートする関数を特定し、グローバル名前空間に入れる方法です。 グローバルな名前空間にインポートされるものを明示的に示しているので、 import * よりも害は少ないですが、単純な import modu より良い唯一の利点は、タイピングを少し省けることです。

とても悪い

[...]
from modu import *
[...]
x = sqrt(4)  # Is sqrt part of modu? A builtin? Defined above?

良い

from modu import sqrt
[...]
x = sqrt(4)  # sqrt may be part of modu, if not redefined in between

ベスト

import modu
[...]
x = modu.sqrt(4)  # sqrt is visibly part of modu's namespace

code_style セクションで述べたように、読みやすさはPythonの主な機能の1つです。読みやすさとは、無用な定型文や混乱を避けることを意味します。したがって、一定のレベルの簡潔さを達成しようと努力しています。しかし、簡潔さとあいまいさは、簡潔さが止まるべき限界です。 modu.func イディオムのように、クラスや関数がどこから来ているのかをすぐに知ることができるので、最もシンプルな単一ファイルプロジェクトだけでは、コードの読みやすさとわかりやすさが大幅に向上します。

パッケージ

Pythonは非常に単純なパッケージシステムを提供しています。これは単純にモジュール機構をディレクトリに拡張したものです。

__init__.py ファイルを持つディレクトリはPythonパッケージとみなされます。パッケージ内のさまざまなモジュールは、普通のモジュールと同様にインポートされますが、パッケージ全体の定義を集めるために使用される __init__.py ファイルの特殊な動作を伴います。

ディレクトリ pack/ のファイル modu.py は、import pack.modu というステートメントでインポートされます。 この文は __init__.py ファイルを pack で探し、すべての最上位レベルの文を実行します。 それから、pack/modu.py という名前のファイルを探し、すべてのトップレベルのステートメントを実行します。 これらの操作の後で、 modu.py で定義された変数、関数、またはクラスは、pack.modu名前空間で使用できます。

よく見られる問題は、 __init__.py ファイルにあまりにも多くのコードを追加することです。 プロジェクトの複雑さが増すと、深いディレクトリ構造にサブパッケージとサブサブパッケージが存在する可能性があります。 この場合、サブサブパッケージから単一の項目をインポートするには、ツリーを走査中に全ての __init__.py ファイルを実行する必要があります。

パッケージのモジュールとサブパッケージがコードを共有する必要がない場合、 __init__.py ファイルを空のままにしておくのは正常であり、良い習慣でもあります。

最後に、深くネストされたパッケージをインポートするための便利な構文があります: import very.deep.module as mod。これにより、 very.deep.module の冗長な繰り返しの代わりに mod を使うことができます。

オブジェクト指向プログラミング

Pythonは、オブジェクト指向プログラミング言語として記述されることがあります。これはやや誤解を招く可能性があり、明確にする必要があります。

Pythonでは、すべてがオブジェクトであり、そのように扱うことができます。これは、たとえば、関数がファーストクラスのオブジェクトであると言うときに意味するものです。関数、クラス、文字列、さらには型はPythonのオブジェクトです。どんなオブジェクトと同様、型を持ち、関数の引数として渡すことができ、メソッドとプロパティを持つことができます。この理解では、Pythonはオブジェクト指向言語です。

しかし、Javaとは異なり、Pythonはオブジェクト指向プログラミングを主なプログラミングパラダイムとして課していません。 Pythonプロジェクトがオブジェクト指向ではないこと、すなわち、クラス定義、クラス継承、またはオブジェクト指向プログラミングに特有の他のメカニズムを使用しないこと、またはごくわずかしか使用しないことは、完全に実行可能です。

さらに、モジュール セクションに見られるように、Pythonがモジュールと名前空間を扱う方法は、開発者に抽象レイヤのカプセル化と分離を保証する自然な方法です。どちらもオブジェクト指向を使用する最も一般的な理由です。 したがって、Pythonプログラマーは、ビジネスモデルで必要とされないときに、オブジェクト指向を使用しないという自由度があります。

不要なオブジェクト指向を避ける理由はいくつかあります。 カスタムクラスを定義することは、いくつかの状態といくつかの機能を結合する場合に便利です。 関数型プログラミングに関する議論で指摘されているように、問題は方程式の “状態” の部分から来ています。

いくつかのアーキテクチャ、通常はWebアプリケーションでは、複数のインスタンスのPythonプロセスが生成され、同時に発生する可能性のある外部要求に応答します。この場合、いくつかの状態をインスタンス化されたオブジェクトに保持することは、世界に関するいくつかの静的情報を保持することを意味し、並行性の問題または競合状態になりがちです。時には、オブジェクトの状態の初期化(通常は __init__() メソッドで行われます)とそのメソッドの1つによるオブジェクト状態の実際の使用の間に、世界が変更された可能性があります。時代遅れです。例えば、要求はメモリ内のアイテムをロードし、ユーザによってそれを読み取りとしてマークすることができる。別のリクエストで同時にこのアイテムの削除が必要な場合は、最初のプロセスがアイテムをロードした後に実際に削除が行われ、削除されたオブジェクトを読み取り済みとしてマークする必要があります。

これと他の問題は、ステートレス関数の使用がより良いプログラミングパラダイムであるという考えにつながりました。

同じことを言うもう一つの方法は、できるだけ暗黙的なコンテキストと副作用の少ない関数とプロシージャを使用することを提案することです。関数の暗黙のコンテキストは、関数内からアクセスされる永続化層のグローバル変数または項目のいずれかで構成されます。副作用とは、関数がその暗黙のコンテキストに対して行う変更です。関数がグローバル変数または永続化層にデータを保存または削除する場合、それは副作用を伴うと言われています。

文脈や副作用を伴う関数をロジックを持つ関数(純関数と呼ぶ)から慎重に分離することで、次のような利点が得られます。

  • 純粋な関数は確定的です: 固定された入力が与えられると、出力は常に同じになります。
  • リファクタリングや最適化が必要な場合は、純関数を簡単に変更または置換することができます。
  • 純粋な関数は単体テストでテストする方が簡単です。後で複雑なコンテキストの設定やデータのクリーニングが不要になります。
  • 純粋な関数は、操作、飾り付け、渡しが簡単です。

要約すると、純粋な関数は、コンテキストや副作用がないため、クラスやオブジェクトよりも効率的なビルディングブロックです。

明らかに、オブジェクト指向は、有用であり、多くの場合必要になります。例えば、操作されるもの (window, buttons, avatars, vehicles) がコンピュータのメモリ内で比較的長い寿命を有するグラフィカルデスクトップアプリケーションまたはゲームを開発する場合などです。

デコレータ

Python言語は、’デコレータ’と呼ばれている、シンプルで強力な構文を提供しています。デコレータは、関数またはメソッドをラップする(またはデコレートする)関数またはクラスです。 「デコレートされた」機能または方法は、元の「デコレートされていない」機能または方法を置き換えます。関数はPythonのファーストクラスのオブジェクトであるため、これは ‘手動で’行うことができますが、@デコレータの構文を使用する方が明確であり、したがって好ましいものです。

def foo():
    # do something

def decorator(func):
    # manipulate func
    return func

foo = decorator(foo)  # Manually decorate

@decorator
def bar():
    # Do something
# bar() is decorated

このメカニズムは、懸念を分離し、関数またはメソッドのコアロジックを「汚染する」外部の関連しないロジックを回避するのに便利です。 デコレーションでうまく処理される機能の良い例は memoization またはキャッシングです: 高価な関数の結果を 既に計算されているときにそれらを再計算する代わりに直接使用することができます。 これは明らかに関数ロジックの一部ではありません。

コンテキストマネージャ

コンテキストマネージャは、アクションに余分なコンテキスト情報を提供するPythonオブジェクトです。 この余分な情報は、 with 文を使って文脈を開始すると同時に、 with ブロック内のすべてのコードを完了したときに呼び出し可能なものを実行するという形で呼び出すことができます。 コンテキストマネージャを使用する最もよく知られている例をここに示し、ファイルを開きます:

with open('file.txt') as f:
    contents = f.read()

このパターンに精通している人は、このように open を呼び出すと、ある時点で fclose メソッドが呼び出されることが保証されます。 これにより、開発者の認知負荷が軽減され、コードが読みやすくなります。

この機能を実装するには、クラスを使用する方法とジェネレータを使用する方法があります。 クラスのアプローチから始めて、上記の機能を自分で実装しましょう:

class CustomOpen(object):
    def __init__(self, filename):
      self.file = open(filename)

    def __enter__(self):
        return self.file

    def __exit__(self, ctx_type, ctx_value, ctx_traceback):
        self.file.close()

with CustomOpen('file') as f:
    contents = f.read()

これは単に with 文で使われる2つの余分なメソッドを持つ普通のPythonオブジェクトです。 CustomOpenが最初にインスタンス化され、 __enter__ メソッドが呼び出され、 __enter__ が返すものは、文の as 部分の f に代入されます。 with ブロックの内容の実行が終了すると、 __exit__ メソッドが呼び出されます。

そして、Python独自のジェネレータを使ったアプローチは contextlib:

from contextlib import contextmanager

@contextmanager
def custom_open(filename):
    f = open(filename)
    try:
        yield f
    finally:
        f.close()

with custom_open('file') as f:
    contents = f.read()

これは上のクラスの例とまったく同じように動作しますが、もっと簡潔です。 custom_open 関数は、 yield ステートメントに達するまで実行されます。次に with 文に制御を戻し、 as 部分に fyield を割り当てます。 finally 節は、 with の中に例外があったかどうかにかかわらず、 close() が呼び出されるようにします。

2つのアプローチが同じように見えるので、PythonのZenに従っていつ使うべきかを決める必要があります。クラスのアプローチは、カプセル化するロジックが相当量ある場合には効果的です。ファンクションのアプローチは、単純なアクションを扱う場合には適しています。

動的タイピング

Pythonは動的に型付けされています。つまり、変数には固定型がありません。 実際、Pythonでは、変数は他の多くの言語、特に静的型の言語とは大きく異なります。 変数は、値が書き込まれるコンピュータのメモリのセグメントではなく、オブジェクトを指す「タグ」または「名前」です。 したがって、変数 ‘a’を値1に設定し、値 ‘文字列’に設定してから関数に設定することができます。

Pythonの動的型付けはしばしば弱点とみなされ、実際には複雑で難しいコードにつながる可能性があります。 ‘a’という名前のものはさまざまなものに設定することができ、開発者や管理者は完全に無関係のオブジェクトに設定されていないことを確認するためにこの名前をコード内で追跡する必要があります。

この問題を回避するガイドラインがいくつかあります。

  • 異なる変数に同じ変数名を使用しないでください。

悪い

a = 1
a = 'a string'
def a():
    pass  # Do something

良い

count = 1
msg = 'a string'
def func():
    pass  # Do something

短い関数やメソッドを使用すると、無関係な2つのものに対して同じ名前を使用するリスクを軽減できます。

異なるタイプのものでも、関連するものであっても、異なる名前を使用する方が良いでしょう。

悪い

items = 'a b c d'  # This is a string...
items = items.split(' ')  # ...becoming a list
items = set(items)  # ...and then a set

名前を再利用すると効率は上がりません。割り当ては新しいオブジェクトを作成する必要があります。 しかし、複雑さが増し、それぞれの割り当てが ‘if’ブランチやループを含む他のコード行で分かれている場合、与えられた変数の型が何であるかを確かめることは難しくなります。

関数型プログラミングのように、変数を再割り当てすることを決して推奨していないコーディングもあります。 Javaでは、これは final キーワードで行います。 Pythonは final キーワードを持っておらず、とにかくその哲学に反するでしょう。しかし、変数に複数回代入するのを避けることは良い規律かもしれませんし、可変で不変な型の概念を理解するのに役立ちます。

変更可能および変更不可能な型

Pythonには、組み込み型とユーザー定義型の2種類があります。

ミュータブルなタイプは、コンテンツのインプレース変更を可能にするタイプです。 典型的なミュータブルはリストと辞書です: 全てのリストには list.append()list.pop() のようなメソッドの変更があります。 辞書についても同じことが言えます。

イミュータブルは、内容を変更するためのメソッドを提供しません。 例えば、変数xに整数6を設定すると、「インクリメント」メソッドはありません。 x + 1を計算する場合は、別の整数を作成して名前を付ける必要があります。

my_list = [1, 2, 3]
my_list[0] = 4
print my_list  # [4, 2, 3] <- The same list as changed

x = 6
x = x + 1  # The new x is another object

この動作の違いの1つの結果として、ミュータブルなタイプは「安定」ではないため、辞書キーとして使用することはできません。

自然に変更可能なものに対しては適切にミュータブルな型を使用し、性質上固定されているものに対してはイミュータブルなタイプを使用すると、コードの意図を明確にするのに役立ちます。

例えば、リストの不変な等価物は (1, 2) で作られたタプルです。このタプルは、インプレースで変更できないペアであり、辞書のキーとして使用できます。

初心者を驚かせることができるPythonの特徴の1つは、文字列が不変であることです。 つまり、パーツから文字列を作成するときには、リスト内に変更可能なパーツを累積して、フルストリングが必要なときにパーツ同士を接着 (‘join’) する方がはるかに効率的です。 しかし、注意すべき点の1つは、リスト内包は、 append() を呼び出してループ内にリストを構築するよりも優れていて速いということです。

悪い

# create a concatenated string from 0 to 19 (e.g. "012..1819")
nums = ""
for n in range(20):
  nums += str(n)   # slow and inefficient
print nums

良い

# create a concatenated string from 0 to 19 (e.g. "012..1819")
nums = []
for n in range(20):
  nums.append(str(n))
print "".join(nums)  # much more efficient

ベスト

# create a concatenated string from 0 to 19 (e.g. "012..1819")
nums = [str(n) for n in range(20)]
print "".join(nums)

文字列について言及する最後の1つは、 join() を使うことが必ずしも最良ではないということです。あらかじめ決められた数の文字列から新しい文字列を作成する場合は、加算演算子を使用するほうが高速ですが、上記のような場合や既存の文字列に追加する場合は join() の方が好ましいでしょう。

foo = 'foo'
bar = 'bar'

foobar = foo + bar  # This is good
foo += 'ooo'  # This is bad, instead you should do:
foo = ''.join([foo, 'ooo'])

注釈

また、 フォーマット演算子を使って、str.join()+ のほかにあらかじめ決められた数の文字列を連結することもできます。 しかし、PEP 3101 は、str.format() メソッドのために % 演算子の使用を奨励しています。

foo = 'foo'
bar = 'bar'

foobar = '%s%s' % (foo, bar) # It is OK
foobar = '{0}{1}'.format(foo, bar) # It is better
foobar = '{foo}{bar}'.format(foo=foo, bar=bar) # It is best

ベンダー依存の依存関係

ランナー