Pythonで関数を書いていて、こんな経験ありませんか?
- 引数に渡したリストが、なぜか呼び出し元でも書き換わっている
- 「さっき動いたのに、今は動かない…」みたいに挙動が安定しない
- バグの原因を追っていたら、思わぬ場所でデータが変わっていた
これ、だいたい犯人は「副作用(side effects)」です。
副作用って聞くとちょっと怖い言葉ですが、要するに「関数が値を返す以外に、外の世界まで勝手に変えちゃうこと」。Pythonは便利な反面、ミュータブル(変更できる)な型やデフォルト引数などの仕様が絡むと、意図せず副作用が発生しやすいんですよね。
この記事では、まず副作用の正体をはっきりさせたうえで、
- なぜPythonで副作用が起きやすいのか
- どこで混入しやすいのか(ミュータブル/エイリアス/デフォルト引数)
- どうやって消す・制御するのか(実践手順つき)
を、順番にやさしく解説します。
読み終わるころには、「副作用=運が悪いと起きるバグ」じゃなくて、設計でコントロールできるものとして扱えるようになります。
小さなスクリプトでも、チーム開発でも、テストを書くときでも効いてくる考え方なので、ぜひ一緒に整理していきましょう😊
1. 副作用の正体:何が問題なのか
Pythonにおける副作用とは、関数が値を返す以外のことをしてしまう状態を指します。 もう少し具体的に言うと、関数の外にあるデータや環境を、知らないうちに書き換えてしまうことです。
たとえば、こんなケースが代表的ですね。
- グローバル変数を関数の中で直接変更する
- 引数として受け取ったリストや辞書をそのまま書き換える
- 関数の中でprint出力やファイル書き込みを行う
これらはすべて関数の外部状態に影響を与えるため、副作用を持つ処理になります。
逆に、入力が同じなら、必ず同じ出力が返ってくる関数は 「純粋関数」と呼ばれます。 純粋関数は外部の状態に一切依存せず、変更もしません。
Pythonはこの純粋関数スタイルを強制しない言語なので、 気づかないうちに副作用を含んだコードを書けてしまいます。 これが便利でもあり、落とし穴でもあるんですね。
副作用が引き起こす3つの問題
副作用が混ざったコードは、最初は動いていても、 規模が少し大きくなると一気に扱いづらくなります。
- デバッグが難しくなる
関数単体を見ても原因が分からず、「どこでデータが変わったのか」を追いかける必要が出てきます。 - テストが書きにくくなる
関数を呼ぶ前の状態を毎回そろえないといけないため、ユニットテストの準備コストが跳ね上がります。 - 予期せぬ挙動を生みやすい
一見関係なさそうな処理が、裏で同じオブジェクトを触っていて事故が起きます。
特にPythonでは、「破壊的に処理しているつもりはないのに、結果的に元データが変わっていた」 という事故がとても起きやすいです。
この感覚を整理するために、破壊的処理/非破壊的処理の考え方を ここで一度押さえておくと理解がスムーズになります。

次の章では、なぜPythonでは副作用が発生しやすいのかを、 言語仕様レベルで分解して見ていきます。 ここが分かると、「気をつけよう」ではなく「起きない書き方」が自然に選べるようになりますよ。
2. 副作用が引き起こす3つの実害
副作用は「理論的によろしくない」という話だけでは終わりません。 実務や日常のスクリプトでも、確実に実害として効いてきます。
ここでは、Pythonで副作用を放置したときに起こりやすい代表的なトラブルを 3つに分けて見ていきましょう。
① デバッグが地獄になる
副作用があるコードの一番つらいところは、 「今、どの状態なのか分からない」ことです。
関数を1回呼んだだけで結果が変わるならまだマシですが、 「呼び出し順によって挙動が変わる」「前に実行した処理に影響される」 といった状態になると、再現が難しくなります。
特に、リストや辞書を引数として渡し、 関数内でそれを書き換えているケースは要注意です。 エラーの原因がまったく別の関数にあるように見えてしまいます。
② テストが書きにくくなる
副作用があると、ユニットテストの前提条件が一気に増えます。
- この関数を呼ぶ前に、どんな状態であるべきか
- どの順番で実行されたか
- 他のテストの影響を受けていないか
本来なら「入力 → 出力」だけを見ればいいはずなのに、 周辺の状態まで含めて考えないとテストできないのは、 かなりのストレスです。
結果として、「テストを書くのが面倒 → 書かれなくなる」 という悪循環に入りがちです。
③ 将来的な保守コストが爆増する
副作用は、コードを書いた本人がいなくなったあとに本気で牙をむきます。
「この関数、何をしているんだろう?」と読んだときに、 返り値だけでなくどこを変更しているかまで追わないと理解できないコードは、 修正するのが怖くなります。
その結果、
- ちょっとした修正でも全体を確認する必要がある
- 安全策として同じ処理がコピーされて増殖する
- 誰も触りたがらない「ブラックボックス」が生まれる
こうして、副作用は技術的負債として積み重なっていきます。

次の章では、こうした実害の元凶になっている Python特有の「副作用を生みやすい原因」を、 具体例と一緒に分解していきます。
3. 副作用を生む「主な原因」
ここまでで、「副作用がなぜ厄介なのか」はかなりイメージできたと思います。 では次に、なぜPythonでは副作用が発生しやすいのかを見ていきましょう。
これは「書き方が悪い」というより、Pythonの言語仕様として起こりやすいポイントが いくつか決まっている、という話です。
特に重要なのが、ミュータブル(変更可能)なオブジェクトの存在です。 ここがあいまいだと、副作用の話はほぼ確実に混乱します。
原因① ミュータブルな組み込み型
Pythonには、中身を書き換えられる型がいくつも用意されています。 代表的なのが次の型です。
- list
- dict
- set
- bytearray
これらを関数の引数として渡し、関数内で変更すると、 呼び出し元が持っているオブジェクトそのものが変わります。
書いている本人は「この関数の中だけで完結しているつもり」でも、 実際には外の世界まで影響が及んでしまう。 これが、Pythonで副作用が生まれる一番多いパターンです。
原因② 変数の別名(エイリアス)
Pythonの代入は、「値のコピー」ではありません。 正確には、同じオブジェクトへの参照を増やしているだけです。
そのため、次のような状況が普通に起こります。
- Aという変数を変更したつもりが、Bも変わっている
- 関数に渡した引数を触ったら、呼び出し元のデータも変わった
これはPythonに慣れていないうちはかなり直感に反します。 でも、ここを理解しないままコードを書くと、 副作用はほぼ避けられません。
原因③ デフォルト引数の罠
Pythonで有名な落とし穴が、 ミュータブルなオブジェクトをデフォルト引数に指定するケースです。
たとえば、L=[] のような書き方。 一見問題なさそうですが、このリストは 関数定義時に1回だけ作られ、呼び出し間で共有されます。
その結果、
- 前回の呼び出し結果が次回に残る
- なぜかデータが増え続ける
といった、かなり不可解な挙動になります。 これも立派な副作用です。

次の章では、ここまで見てきた原因を踏まえて、 副作用を消す・制御するための具体的な手順を 実際の書き方ベースで整理していきます。 「気をつける」ではなく「仕組みで防ぐ」方法です。
4. 副作用を消す・制御するための実践的手順
ここからは実践編です。 副作用は「注意すれば避けられるもの」ではなく、 書き方と設計で仕組み的に防ぐものだと考えるとラクになります。
以下の手順は、初心者〜中級者がまず身につけておきたい 副作用対策の基本セットです。
手順1:引数を直接変更せず、新しいオブジェクトを返す
一番シンプルで効果が高い方法がこれです。 「元のデータは触らない」をルールにします。
やり方は難しくありません。
- 関数内で新しいリストや辞書を作る
- 処理結果をそこに詰める
- 完成した新しいオブジェクトを return する
この形にしておくと、関数は 入力 → 出力だけに責務が限定され、 呼び出し元の状態を汚しません。
なお、リスト内包表記や辞書内包表記を使うと、 このパターンはかなり簡潔に書けます。
手順2:デフォルト引数には「Noneパターン」を使う
デフォルト引数の罠を避けるための定番テクニックが、 Noneパターンです。
考え方は次の3ステップだけ。
- デフォルト値は None にする
- 関数冒頭で is None をチェックする
- 必要なら、関数内で新しいオブジェクトを作る
これだけで、「呼び出し間でデータが共有される」という 最悪の副作用を確実に防げます。
Pythonでは、この書き方が 事実上のベストプラクティスとして定着しています。
手順3:イミュータブルな代替手段を積極的に使う
そもそも「変更できなければ、副作用は起きない」です。 そこで活躍するのがイミュータブル(変更不能)な型です。
- tuple
順序付きデータで、あとから変更しないなら list の代わりに。 - frozenset
集合操作が必要だけど、中身は固定したい場合に。 - frozen dataclass
設定値やデータ構造を「絶対に変えたくない」意思表示として使える。 - NamedTuple
名前付きで読みやすく、かつ不変なデータ構造を作りたいときに便利。
「後で変えるかも…」と迷ったら、 まずはイミュータブルで作るくらいがちょうどいいです。
手順4:防御的コピー(Defensive Copying)を行う
どうしてもミュータブルなオブジェクトを扱う必要がある場合は、 最初にコピーを作るという選択肢があります。
代表的な方法は次の通りです。
- copy.copy()(浅いコピー)
- copy.deepcopy()(深いコピー)
- リストのスライス(
new_list = old_list[:]) - dict.copy()
これにより、「操作はするが、元データは守る」 という境界線をはっきり引けます。

次の章では、ここまでのテクニックを一段引いた視点でまとめ、 設計としてどう副作用と向き合うかを整理します。 ここが理解できると、コードの安定感が一気に変わります。
5. 【ここが最重要】設計の視点で副作用を減らす考え方
ここまで、テクニックとしての「副作用の消し方」を見てきました。 でも実は、一番大事なのは書き方よりも考え方です。
副作用が多いコードは、たいてい 「この関数、どこまで責任を持つの?」が曖昧です。
値を計算するのか、状態を変更するのか、表示するのか。 これらが一つの関数に混ざると、副作用は一気に増えます。
「動くコード」と「壊れにくいコード」は別物
Pythonはとても柔軟なので、 「とりあえず動くコード」はすぐに書けます。
でも、副作用を放置したコードは、 あとから壊れやすいコードになりがちです。
- 少し修正しただけで別の場所が壊れる
- 影響範囲が読めず、変更が怖くなる
- 結果として、誰も触らなくなる
副作用を減らす設計は、 未来の自分やチームを助けるための投資とも言えます。
副作用を減らすと、何が楽になるのか
副作用が少ない関数は、 自然と次のような性質を持ちます。
- 関数単体で理解できる
- 入力と出力だけを見ればテストできる
- 他のコードから安全に再利用できる
これは偶然ではなく、 「責務を絞った結果」です。
この考え方を体系的に学ぶのに、とても相性がいい一冊があります。
リーダブルコード(第2版)
副作用を減らす関数設計、読みやすさ、変更しやすさといった 「長く使えるコード」の考え方を、具体例ベースで学べる定番書です。

Python専用の本ではありませんが、 副作用に悩み始めたタイミングで読むと、 「あ、これのことか」と腑に落ちる場面がかなり多いです。
6. 副作用を消すことで得られるメリット
ここまで読んでいただいて、 「副作用って、単なるバグの話じゃないんだな」 と感じてもらえていたら嬉しいです。
副作用を意識して減らしていくと、 コードの性質そのものが、少しずつ変わっていきます。
モジュール性が上がる
副作用が少ない関数は、 その関数だけを切り出して使えるようになります。
他の処理や状態に依存しないため、 小さな部品として組み合わせやすく、 再利用もしやすくなります。
テストとデバッグが圧倒的に楽になる
純粋関数に近い形になれば、 テストはとてもシンプルになります。
「この入力を渡したら、この出力になるか」 それだけを確認すればいいからです。
これは、バグを直すときにも同じで、 「関数を呼ぶ前の状態」を再現する必要がなくなります。
保守性・変更耐性が高まる
副作用を減らすということは、 影響範囲を狭めるということでもあります。
修正が必要になったときに、 「ここを変えたら、どこが壊れるだろう…」 と不安にならなくて済むコードは、 長く付き合えるコードです。
この考え方は、リファクタリングとも非常に相性がいいです。
「書くスピード」も、実は落ちない
副作用を避けると、 コードを書くのが遅くなると思われがちですが、 実際は逆です。

後からバグを追いかける時間や、 修正のたびに全体を確認する時間が減るため、 トータルでは確実に速くなります。
まとめ
この記事では、Pythonにおける「副作用」の正体と、 それをどうやって消す・制御するかを見てきました。
- 副作用とは「関数が外の状態まで変えてしまうこと」
- Pythonではミュータブルな型や参照の仕組みで起きやすい
- Noneパターンや新しいオブジェクトを返す設計で防げる
- 一番大事なのは「関数の責務を絞る」設計の視点
副作用は、運が悪いと起きるバグではありません。 設計の結果として生まれるものです。
だからこそ、書き方と考え方を少し変えるだけで、 驚くほどコードは安定します。
小さなスクリプトでも、 「この関数は何を返すだけの存在か?」 と一度立ち止まるだけで、副作用はかなり減らせます。
今日からすべて完璧にする必要はありません。 まずは「引数を直接いじっていないか」を 意識するところから始めてみてください。
参考文献・参考リンク
- Python公式ドキュメント:用語集「副作用(side effect)」
- Python公式ドキュメント:実行モデル(Execution Model)
- Python公式チュートリアル:関数の定義
- Python公式チュートリアル:クラス
- Python公式ドキュメント:組み込み関数とオブジェクトID
- Python公式ドキュメント:copy モジュール(浅いコピー・深いコピー)
- Python公式ドキュメント:dataclasses(frozen dataclass)
- Python公式ドキュメント:functools モジュール
- Python公式 HOWTO:関数型プログラミングのヒント
- Python公式ドキュメント:typing モジュール
- PEP 20:The Zen of Python(Pythonの哲学)
- Real Python:Mutable vs Immutable Types in Python
- Real Python:Defining Your Own Python Functions
- Qiita:「Python 副作用」に関する記事一覧
- Zenn:「Python 副作用」に関する記事一覧
よくある質問(Q&A)
- Q副作用は完全になくすべきですか?
- A
必ずしもゼロにする必要はありません。 重要なのは「どこで副作用が起きるか分かっている状態」にすることです。
I/O処理やログ出力など、副作用が必要な場面もあります。 それらをロジックと分離できていれば問題ありません。
- Q小規模なスクリプトでも意識したほうがいい?
- A
はい、むしろ小規模なうちから意識すると効果が大きいです。
後から機能を足したり、数か月後に自分で読み返したときに、 「安心して触れるコード」になります。
- Qパフォーマンスと副作用回避はトレードオフですか?
- A
ケースによりますが、多くの場合は気にする必要はありません。
まずは可読性と安全性を優先し、 本当に問題になったときだけ最適化を考えるのが現実的です。
副作用を減らす設計は、 パフォーマンス以前にバグを減らす効果が圧倒的に大きいです。









※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。
※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。