Skip to content
This page has been auto-translated and may contain errors.View in English

辞書

docs.scrimba.com

リストは位置で項目を検索できます。しかし、多くの場合は名前で検索したいものです。「3番目の項目をくれ」ではなく、「太郎のスコアをくれ」といった感じです。辞書はキー値ペアとしてデータを保存します。位置ではなく、キーで値を検索します。

リストの位置インデックスが意味のない場合、辞書が正しい構造です。辞書は任意のキーを値にマッピングし、O(1)の時間で名前付きの検索を実現します。ランキング、JSON応答、設定ファイル:これらはすべてキー値マッピングとして自然に表現されます。

dictは平均的にO(1)の検索、挿入、削除が可能なキー値ストアです(O(1)は、キーがハッシングによって検索されるため、辞書がどれほど大きくなってもコストが一定のままという意味です。ハッシングはキーを番号に変換し、その番号がスロットを直接指します)。キーはハッシュ可能である必要があります。つまり、その内容は変わらないため、その番号は安定のままです。strinttupleは適格ですが、listはそうではありません。値は任意のオブジェクトです。Python 3.7以降、辞書は挿入した順序でキーを保持します。これはシリアル化や表示の際に重要です。同じ構造は、あなた自身のオブジェクトの属性を含む、Pythonの多くの内部機能を支えているので、辞書はよく知る価値があります。リストの章は位置ベースの相手をカバーしています。

辞書の作成

各キーと値の間にコロンがある波括弧で、ペア間にコンマがあります。キーはほぼ常に文字列です。値は何でもできます:数値、文字列、他のリスト、さらに他の辞書。

辞書リテラルは波括弧でkey: value構文を使います。キーは任意の不変(ハッシュ可能)型になります:文字列、整数、タプル。値は任意のPythonオブジェクトです。辞書は挿入順序を保持するため、反復処理すると、追加した順序で項目を取得します。

辞書リテラルは左から右に評価されます。キーはハッシュ可能である必要があります(その内容は変わらないため、Pythonがそれをファイルする番号は一定のままです):strinttupleは適格ですが、listdictはそうではありません。値にはそのような制限はありません。挿入順序はPython 3.7以降の言語保証の一部であるため、反復処理とシリアル化は再現可能です。静かな罠の1つ:単一のリテラル内の重複キーは例外を発生させず、最後の値を保持し、前の値を削除します。

python
player = {
    "name": "太郎",
    "score": 87,
    "level": 5,
    "alive": True,
}
Juno辞書の作成 波括弧、各キーとその値の間のコロン、ペア間のコンマ。キーは通常は文字列、値は何でもできます。別の辞書でさえ。何かを追加した順序がその順序で戻ってくるもので、私が期待していた以上に使っています。
Juno辞書の作成 波括弧でkey: valueペア。キーは任意の不変型(strinttuple)、値は任意のオブジェクト。反復処理は挿入順序に従うため、順序が重要な場合、辞書は問題ありません。
Juno辞書の作成 キーはハッシュ可能である必要があります。したがってstrinttupleは入りますが、listdictは出ません。挿入順序は3.7以降で保証されているため、出力に依存できます。重複キーのリテラルに注意してください。警告なしで最後の値を使用します。

値へのアクセス

キーを持つ角括弧を使って値を取得します。キーが存在しない場合、PythonはKeyErrorを発生させます。キーがそこにあるかどうか確信がない場合は、.get()を使用してください:クラッシュする代わりにNoneを返します、または指定したデフォルト値を返します。

角括弧アクセスは、キーが見つからない場合にKeyErrorを発生させます。.get(key)はミスでNoneを返します。.get(key, default)は代わりにデフォルトを返します。キーの存在が不確かな場合は常に.get()を使用してください。アクセスをtry/exceptでラップするよりも安全で読みやすいです。

角括弧アクセスはキーをハッシング化して一致するスロットを読むことで検索します。これは平均的にO(1)です(サイズに関係なくコストは一定)。ミスでKeyErrorを発生させます。.get(key, default=None)は同じ検索を行いますが、発生させる代わりにミスでデフォルトを返すため、キーの存在が不確かな場合の正しい選択です。角括弧アクセスの前に保護するには、key in dもO(1)です。inを使用するのは、はい・いいえだけが必要な場合、.get()を使用するのは、値またはフォールバックが1ステップで必要な場合です。

python
player = {"name": "太郎", "score": 87}

player["name"]    # "太郎"
player["score"]   # 87
player["lives"]   # KeyError (キーが存在しない)
python
player.get("score")          # 87
player.get("lives")          # None (エラーなし、デフォルトではNoneを返す)
player.get("lives", 3)       # 3   (キーが無い場合はこのデフォルトを使用)

.get()はキーが見つからない可能性がある場合に安全です:

python
count = inventory.get("arrows", 0)   # 辞書に"arrows"がない場合は0
Juno値へのアクセスd["key"]は値を渡しますが、そのキーがない場合はKeyErrorを発生させます。確実でない場合は、.get()に手を伸ばします。クラッシュする代わりにNoneを返すか、渡したデフォルトを返します。今はすべてをデフォルトにしており、プログラムは見つからないキーでのクラッシュをやめました。
Juno値へのアクセス 角括弧アクセスはミスでKeyErrorを発生させます。.get(key)Noneを返します。.get(key, default)は渡したものを返します。キーが不在の可能性がある場合は.get()を使用してください。アクセスをtry/exceptでラップするよりも読みやすいです。
Juno値へのアクセスd[key].get()は両方とも1つのO(1)検索を実行します。違いはミスで何が起こるか:発生させるか、デフォルトを返すか。key in dを使用するのは、はい・いいえが必要な場合。.get()を使用するのは、値またはフォールバックが1ステップで必要な場合。

追加と更新

角括弧を使ってキーに割り当てます。キーが既に存在する場合、値は置き換えられます。まだ存在しない場合、新しいエントリが作成されます。.update()を使用して、別の辞書全体を一度にマージします。

キーへの割り当ては平均的にはO(1)で、両方のジョブを実行します。キーが新しい場合はエントリを作成し、既に存在する場合は値を置き換えます。.update()は別の辞書またはキー値ペアのイテラブルを受け入れ、各エントリに対して同じ割り当てを適用し、共有キーに上書きします。

d[key] = valueはキーをハッシング化し、1つのO(1)平均ステップで挿入または上書きします。したがって、別の「追加」と「更新」はありません。割り当ては両方を実行します。.update(other)other内の各エントリに対して同じ操作であり、共有キーに上書きします。元のオリジナルを変更せずにマージする場合、|演算子(Python 3.9+)は新しい辞書を返し、両方の入力は変更されません。一方、|=.update()のようにその場で変更します。関数が呼び出し元がまだ保持している辞書を変更しないようにするには、|に手を伸ばします。

python
player = {"name": "太郎", "score": 87}

player["score"] = 92        # 既存のものを更新
player["level"] = 5         # 新しいキーを追加
python
extras = {"level": 5, "alive": True}
player.update(extras)   # extrasのキーで追加/上書き
Juno追加と更新 角括弧を使ってキーに割り当てます。既に存在する場合は値が置き換えられます。存在しない場合は新しいエントリが表示されます。1つの構文が両方を処理するため、事前にチェックする必要はありません。別の辞書全体を一度にマージするには.update()を使用してください。
Juno追加と更新d[key] = valueは1つの移動で作成または置き換えるため、異なる追加と更新はありません。.update(other)は辞書全体をマージし、共有キーに上書きします。両方とも1キーあたりO(1)です。
Juno追加と更新 割り当ては1つのO(1)ステップで挿入または上書きし、.update()はエントリごとにそれです。元のオリジナルに触れたくない場合は、|は新しくマージされた辞書を返し、|=はその場で変更します。関数内で|を使用して、呼び出し元がまだ保持している辞書を静かに変更しないようにしてください。

アイテムの削除

エントリを削除する4つの方法があります。.pop()はキーを削除して値を返します。デフォルト付きの.pop()は、キーが見つからない可能性がある場合に安全です。delはキーを削除して戻り値はありません。.clear()は辞書全体を空にします。

.pop(key)はミスでKeyErrorを発生させます。.pop(key, default)は代わりにデフォルトを返すため、安全な削除メソッドです。del d[key]はキーを削除して戻り値がなく、ミスでKeyErrorを発生させます。.clear()はすべてのエントリを削除しますが、辞書オブジェクト自体は保持します。

.pop(key, default)は1つのO(1)平均検索で削除して返し、デフォルトはミスセーフにします。なしで、見つからないキーはKeyErrorを発生させます。del d[key]は戻り値なしで削除し、ミスで発生させます。.clear()は辞書を空にしますが、同じオブジェクトを保持するため、それを指す他の名前は空の辞書を見ます。覚える失敗モード:反復処理中に辞書を変更すると、ループの途中でRuntimeErrorを発生させます。修正は、最初に削除する予定のキーをスナップショットし、for key in list(d):で、元のオブジェクトから削除します。

python
player = {"name": "太郎", "score": 87, "level": 5}

player.pop("level")            # "level"を削除して5を返す
player.pop("lives", None)      # 安全なpop、キー不在の場合はNoneを返す
del player["score"]            # "score"を削除、戻り値なし
player.clear()                 # すべてを削除

デフォルト付きの.pop()は、見つからない可能性があるキーを削除する最も安全な方法です。

Junoアイテムの削除.pop(key)はキーを削除して値を返します。.pop(key, None)はキーが見つからない可能性がある場合でも落ち着いています。del d[key]は何も返さずに削除します。.clear()はすべてを空にします。.pop()のデフォルトは、多くのKeyErrorの驚きから私を救いました。
Junoアイテムの削除.pop(key)はミスで発生させます。.pop(key, default)は発生させません。これが安全な選択になります。del d[key]は戻り値なしで削除し、ミスで発生させます。.clear()は辞書を空にしますが、オブジェクト自体は保持します。
Junoアイテムの削除.pop(key, default)はあなたのミスセーフな削除および返却です。delと角括弧pop は見つからないキーで発生させます。咬む1つ:反復処理中に削除するとRuntimeErrorをスローします。最初にfor key in list(d):でスナップショットし、元のオブジェクトから削除します。

反復処理

3つのビューで、辞書のさまざまな部分をループさせることができます。辞書を直接反復するとキーが得られます。.values()は値を与えます。.items()は両方を一度に与え、これが最も多く使用するものです。各ペアを2つの名前にアンパックして、きれいで読みやすいループを作成します。

.keys().values().items()ビューオブジェクトを返します。リストではなく、ビューは辞書の現在の状態を動的に反映します。辞書を変更するとビューは直ちに更新されます。.items()for k, v in d.items()のタプルアンパックが読みやすいため、ほとんどのループに最も有用です。

.keys().values().items()ビューオブジェクトを返します(辞書への生きたウィンドウ、別のリストではなく)。ビューは何もコピーしておらず、辞書の現在の状態を反映しているため、辞書が変更されるとビューも変更されます。キーはユニークでハッシュ可能であるため、キービューはセット代数をサポートします。d.keys() & other.keys()は共有キーを検出し、-は差分を検出します。これは2つの設定を比較するのに便利です。同じ反復処理中の変更罠がここに適用されます。ループ中に辞書を変更する必要がある場合は、list(d.items())でスナップショットを反復処理します。

python
player = {"name": "太郎", "score": 87, "level": 5}

for key in player:               # キーを反復処理(最も一般的)
    print(key)

for key in player.keys():        # 同じ、明示的なキービュー
    print(key)

for value in player.values():    # 値
    print(value)

for key, value in player.items():   # 両方、最も有用
    print(f"{key}: {value}")

.items()が最も使用するものです。各ペアを2つの名前にアンパックするとループが読みやすくなります。

Juno反復処理 辞書を直接ループするとキーが得られます。.values()は値を与えます。.items()は両方を与え、これが最も多く使用するものです。for key, value in player.items()のように各ペアを2つの名前にアンパックすることで、ループが非常に読みやすくなりました。
Juno反復処理.keys().values().items()は生きたビューを返します。リストではなく、辞書が変わるときに追跡します。.items()for k, v in d.items()が最もクリーンに読め、最も頻繁に書くループです。
Juno反復処理 3つのビューは生きたウィンドウで、コピーではなく、辞書が変わるときに反映されます。キービューはセット代数を実行するため、d.keys() & other.keys()は2つの辞書を1行で比較します。削除と同じルール:反復処理中に変更しないでください。必要な場合はlist(d.items())でスナップショットします。

メンバーシップの確認

inは、キーが辞書に存在するかどうかをチェックします。値をチェックするのではなく、キーのみをチェックします。何かが存在しないかどうかをチェックするには、not inを使用してください。

innot inは辞書の場合O(1)で、キーのみをチェックします。値をチェックするには、in d.values()を使用しますが、これはO(n)です。値はインデックスされていないためです。

key in dはキーをハッシング化して1つのスロットを読むことで検出します。平均的にはO(1)で、辞書がどのくらい大きくても費用は一定です。value in d.values()は値を1つずつ歩く必要があります。O(n)で、コストはサイズとともに増加します。その非対称性は、検索する対象をキーとして保存するのではなく値をスキャンする全体的な理由です。値をよく検索する場合、おそらく辞書を別の方法でキー化するか、値をキーにマップバックする2番目の辞書を持つことをお勧めします。

python
player = {"name": "太郎", "score": 87}

"name"  in player      # True
"lives" in player      # False
"lives" not in player  # True

inはキーのみをチェックします。値をチェックするには、in player.values()を使用します。ただし、これはめったに必要ありません。

Junoメンバーシップの確認inはキーが辞書にあるかどうかを示し、キーのみです。辞書がどのくらい大きくなっても速いままです。not inに反転して、何かが不在かどうかをチェックします。
Junoメンバーシップの確認innot inはキーのみをチェックしており、O(1)です。値をチェックするにはvalue in d.values()が必要です。これはO(n)です。値はインデックスされていないためです。したがって、キーで検索する設計を考えてください。
Junoメンバーシップの確認key in dはO(1)で、value in d.values()はO(n)です。そのギャップは、何を検索するかで辞書をキー化する理由です。値を常にスキャンしている場合は、辞書が間違った方法です。代わりにリバースマップを追加してください。

ネストされた辞書

値は辞書そのものになることができます。これは、複数のレベルで構造化されたデータを表現する方法です。統計情報を持つプレイヤー、サブセクション付きの設定ファイル。2セットの角括弧がネストされた値にアクセスします。1つ目は外側のキーを選び、2つ目は内側のキーを選びます。

ネストされた辞書は、値が辞書である辞書です。チェーン連鎖サブスクリプトでアクセスします。内側の辞書を変更すると、外側の辞書も変更されます。外側の辞書が同じオブジェクトへの参照を保有しているためです。可能な限りネストを浅く保つ:深いネストはすぐに読むのが難しくなり、ナビゲートするのが難しくなります。

ネストされた辞書は、内側の辞書への参照を保有します。コピーではなく(参照は1つの共有オブジェクトへのポインタで、2つの名前が同じ内側の辞書を指すことができます)。ここでコピーが咬みます。d.copy()浅いコピーです。外側の辞書を複製しますが、内側の辞書は共有されます。そのため、コピーを通じて変更すると、元のオブジェクトでも変更されます。コピーが完全に独立する必要がある場合、copy.deepcopy()はツリー全体を歩き、すべてのレベルを複製します。設定を変更する可能性があるコードに渡す前に手を伸ばします。各角括弧ステップはそれ自身のO(1)検索です。そのため、深さはスピードでコストがかかりません。読みやすさでのみ。

python
users = {
    "太郎": {"score": 87, "level": 5},
    "花子": {"score": 74, "level": 3},
}

users["太郎"]["score"]   # 87
users["花子"]["level"]     # 3

チェーン連鎖された角括弧でアクセスします。深くネストされた構造の場合、これは厄介になる可能性があるため、可能な限りネストを浅く保つようにしてください。

Junoネストされた辞書 値は独自の辞書全体にすることができます。これは、統計情報セクションを持つプレイヤーのような構造化されたデータを保存する方法です。2セットの括弧に手を伸ばします。最初は外側のキーを選び、2番目は内側から選びます。便利ですが、深くネストしすぎないようにしています。読むのが面倒になります。
Junoネストされた辞書 ネストされた辞書は値が辞書である辞書です。チェーン連鎖された括弧で到達します。外側の辞書は内側の辞書への参照を保有するため、内側の辞書を変更すると、参照されている場所に表示されます。ネストを浅く保つか、ナビゲートするのが難しくなります。
Junoネストされた辞書 外側の辞書は内側の辞書への参照を保存するため、.copy()は浅いです。コピーはそれらの内側の辞書を共有し、ミューテーションは両方に漏らされます。本当の独立が必要な場合は、copy.deepcopy()を使用してください。深さはスピード時のコストはありません。読みやすさでのみです。

setdefault

.setdefault()はキーが存在する場合は読み取り、存在しない場合はデフォルト値に設定します。その後、値を返します。キーが存在する必要があるが、既にある場合は上書きしたくない場合に便利です。

.setdefault(key, default)は1つの呼び出しで読み取りまたは作成です。キーが存在する場合は、何も変更せずに現在の値を返します。存在しない場合は、デフォルトを挿入して返します。一般的なユースケースは、別の存在チェック無しでグループ化された構造を構築することです。

.setdefault(key, default)は読み取り又は作成の1つのO(1)検索です。存在するキーは既存の値を変更せずに返し、不在のキーはdefaultを挿入して返します。注意する価値がある罠:defaultは通常の引数であるため、キーが既に存在する場合でも常に作成されます。そのデフォルトが構築するのに高価な場合(新しいオブジェクト、関数呼び出し)、すべての呼び出しに対してそれを払います。defaultdictはこの次で説明するように、ミス時にのみファクトリーを実行することでこれを回避します。日常的な「リスト内の項目をグループ化」パターンの場合、.setdefault(key, []).append(...)key in dチェックを置き換える標準的なワンライナーです。

python
inventory = {}

inventory.setdefault("arrows", 0)    # "arrows": 0を設定し、0を返す
inventory.setdefault("arrows", 10)   # "arrows"は既に存在します。変更なし。0を返す

キー存在を最初にチェックすることなく、グループ化された構造を構築するのに便利です:

python
groups = {}

for name, team in players:
    groups.setdefault(team, []).append(name)
Junosetdefault.setdefault(key, default)はキーが存在する場合は読み取り、そこにない場合はデフォルトに設定して返します。キーが存在することを確認する良い方法で、既にある値を消さないようにしてください。ほとんどの場合、物をグループ化するために使用します。「このキーがここにあるか」チェックは必要ありません。
Junosetdefault.setdefault(key, default)は既存の値を変更せずに返すか、ミスでデフォルトを挿入して返します。見返りはグループ化です。d.setdefault(k, []).append(x)は別の存在チェックなしでキーごとのリストを構築します。
Junosetdefault 1つのO(1)の読み取りまたは作成ですが、デフォルトはヒット時でも常に構築されるため、高価なデフォルトはすべての呼び出しに対してコストがかかります。これは正確に`defaultdict`のミスオンファクトリーが修正するものです。グループ化する場合、.setdefault(k, []).append(x)key in dチェックを廃止するワンライナーです。

collections.defaultdict とCounter

標準ライブラリには、一般的なパターンを自動的に処理する2つの辞書サブクラスがあります。defaultdictは見つからないキーのデフォルト値を作成するため、KeyErrorは発生しません。Counterは、シーケンス内の各アイテムが表示される頻度を数え、結果を辞書として提供します。

defaultdictは新しいキーのデフォルト値を生成するコール可能な値を取り、.setdefault()の必要を排除します。Counterは頻度カウント用にチューニングされた特殊な辞書で、.most_common()メソッドを備えています。両方とも辞書サブクラスであるため、すべての標準辞書操作はそれらで機能します。

defaultdict(factory)は、欠落キーを初めて読むたびにファクトリー(listintのようなゼロ引数コール可能)を実行し、結果を保存して返すため、d[k]KeyErrorを発生させません。それは.setdefault()との主な違いです。ファクトリーはミスにのみ発火するため、高価なデフォルトはよくあるパスでコストがかかりません。フラグを立てる1つの落とし穴:見つからないキーを読むだけで作成されるため、単にd[k]をチェックしても辞書が成長する可能性があります。テストのみ、挿入しない場合はk in dを使用してください。Counterはカウント用に構築されたdictです。シーケンスをフィードしてそれは出現をカウントし、.most_common(n)は上位nをカウント別に返します。両方はcollectionsモジュールに存在します。

defaultdictCounterは標準ライブラリに存在するため、最初にインポートする必要があります。インポートはモジュールの章で完全に扱われます。

python
from collections import defaultdict

groups = defaultdict(list)
for name, team in players:
    groups[team].append(name)   # チームが新しい場合、KeyErrorはなし
python
from collections import Counter

words = ["cat", "dog", "cat", "bird", "cat", "dog"]
counts = Counter(words)
# Counter({'cat': 3, 'dog': 2, 'bird': 1})

counts.most_common(2)   # [('cat', 3), ('dog', 2)]

Counterは多くの「ループ内の物をカウント」のボイラープレートを保存します。

Junocollections.defaultdict とCounterdefaultdictは見つからないキーに対してデフォルトを埋めるため、グループ化とカウントは`KeyError`を投げなくなります。`Counter`は、シーケンス内の各アイテムが表示される頻度を数え、辞書として戻します。最初に手書きカウントループを`Counter`に交換したとき、コードの半分は消えました。
Junocollections.defaultdict とCounterdefaultdict(list)またはdefaultdict(int)は新しいキーのデフォルトを自動作成し、グループ化とカウント用の`.setdefault()`を廃止します。`Counter`は頻度用にチューニングされた辞書で、`.most_common(n)`が組み込まれています。両方とも辞書サブクラスであるため、ご存じのことはすべて機能します。
Junocollections.defaultdict とCounterdefaultdictのファクトリーはミスにのみ実行されるため、`.setdefault()`とは異なり、高価なデフォルトは一般的なパスのコストはありません。罠:見つからないキーを読むと、作成されるため、テストにはk in dを使用してください。Counterはシーケンスをカウントし、.most_common(n)はトップnを引き出します。

実践で

スコアトラッカーを構築し、すべてのエントリを含むサマリーを印刷します:

python
scores = {"太郎": 87, "花子": 74, "三郎": 92, "四郎": 55}

total = sum(scores.values())
average = total / len(scores)

print(f"プレイヤー数:  {len(scores)}")
print(f"平均:  {average:.1f}")
print(f"最高:  {max(scores.values())}")
print(f"最低:   {min(scores.values())}")
print()

for name, score in scores.items():
    print(f"  {name}: {score}")

ループ内でファイルごとの結果の辞書を構築してから、すべてのエントリ全体でサマリーします:

python
job_results = {}
files = ["report_jan.csv", "report_feb.csv", "report_mar.csv"]

for filename in files:
    size = len(filename) * 100   # ファイルの実際のサイズのプレースホルダー
    if size < 2000:
        status = "ok"
    else:
        status = "large"
    job_results[filename] = {"size": size, "status": status}

ok_count = 0
large_count = 0

for result in job_results.values():
    if result["status"] == "ok":
        ok_count += 1
    else:
        large_count += 1

print(f"{len(job_results)}ファイルを処理:{ok_count} ok、{large_count}大")

必須フィールドをイテレートしてネストされたリクエスト辞書を検証してから、フィーチャー重要度辞書をその場で正規化します:

python
request = {
    "method": "POST",
    "path": "/users",
    "headers": {"Content-Type": "application/json"},
    "body": {"username": "太郎", "email": "[email protected]"},
}

body = request["body"]
errors = []

for field in ["username", "email"]:
    if not body.get(field):
        errors.append(f"必須フィールドが見つかりません:{field}")

if "email" in body and "@" not in body["email"]:
    errors.append("無効なメール形式")

print(f"メソッド:{request['method']} {request['path']}")
if errors:
    print(f"エラー:{errors}")
else:
    print("検証に合格")

# フィーチャー重要度値を正規化して合計1にします
feature_importance = {"age": 0.34, "income": 0.28, "region": 0.15, "purchases": 0.23}
total = sum(feature_importance.values())

for key in feature_importance:
    feature_importance[key] = round(feature_importance[key] / total, 3)

print(f"正規化:{feature_importance}")