実現したいこと
関数を呼び出すたびに新しいリストが作成される関数を作成したいです。
前回の関数呼び出しの結果が残らないようにしたいです。
前提
Pythonでリスト操作を行う関数を作成しています。
デフォルト引数に空のリストを指定して、要素を追加する機能を実装しました。
しかし、関数を複数回呼び出すと前回の呼び出し時に追加した要素が残ってしまっています。
発生している問題・エラーメッセージ
エラーメッセージはなし
該当のソースコード
Python
1def add_item(item, items=[]): 2 items.append(item) 3 return items 4 5# 使用例 6list1 = add_item("test1") 7print("1回目:", list1) # 期待値: ['test1'] 8 9list2 = add_item("test2") 10print("2回目:", list2) # 期待値: ['test2'] だが、 ['test1', 'test2']になる
試したこと
ここの部分が悪そう、ということは色々変更してみてそのように思ったため切り取っていますが、ここの部分は合っていた場合や情報が不足しているなど、必要があれば他の部分も載せます。
補足情報(FW/ツールのバージョンなど)
Python 3.9.7
開発環境: VSCode
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答5件
0
ベストアンサー
公式のFAQに説明があります。
この場合、
python
1def add_item(item, items=None): 2 if items is None: 3 items = []
などとする必要があります。
投稿2025/08/28 01:22
編集2025/08/28 01:22総合スコア22002
0
デフォルトパラメータに関してはリファレンスに記載があります。
The Python Language Reference: 8.6. Function definitions
Default parameter values are evaluated from left to right when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same "pre-computed" value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended. A way around this is to use None as the default, and explicitly test for it in the body of the function, e.g.:
python
1def whats_on_the_telly(penguin=None): 2 if penguin is None: 3 penguin = [] 4 penguin.append("property of the zoo") 5 return penguin
関数を呼び出すたびに新しいリストが作成される関数を作成したいです。
今回は新しいリストを作成したいということなので、list.append()
メソッドではなく list.__add__()
メソッドで新規のリストを作成する様にしてもよいかと思います。
python
1def add_item(item, items=[]): 2 return items + [item] 3 4# 使用例 5list1 = add_item("test1") 6print("1回目:", list1) 7 8list2 = add_item("test2") 9print("2回目:", list2) 10 11list3 = add_item("test3", list2) 12print(f'3回目: {list2 = }, {list3 = }') 13 141回目: ['test1'] 152回目: ['test2'] 163回目: list2 = ['test2'], list3 = ['test2', 'test3']
追記
デコレータ(PEP 318 – Decorators for Functions and Methods)を利用して実装してみました。
関数の属性(attribute) __default_parameters__
にデフォルトパラメータを記録しておいて、関数が実行される度に必要に応じて関数定義時のデフォルト値に戻します。
python
1import inspect 2from functools import wraps 3from copy import deepcopy 4 5def reset_default_parameter(f): 6 @wraps(f) 7 def wrapper(*args, **kwds): 8 if not hasattr(f, '__default_parameters__'): 9 # 属性 __default_parameters__ にデフォルトパラメータを記録 10 f.__default_parameters__ = { 11 k: (i, v.default) for i, (k, v) in enumerate(inspect.signature(f).parameters.items()) 12 if v.default is not inspect.Parameter.empty 13 } 14 for k, (i, v) in f.__default_parameters__.items(): 15 # キーワードパラメータとして指定されていない、かつ、位置パラメータでも 16 # 指定されていない場合に関数定義時のデフォルト値に戻す 17 if k not in kwds and i >= len(args): 18 kwds.update({k: deepcopy(v)}) 19 return f(*args, **kwds) 20 return wrapper 21 22@reset_default_parameter 23def add_item(item, items=[]): 24 items.append(item) 25 return items 26 27if __name__ == '__main__': 28 # 使用例 29 list1 = add_item("test1") 30 print("1回目:", list1) 31 32 list2 = add_item("test2") 33 print("2回目:", list2) 34 35 list3 = [1, 2, 3] 36 add_item('test3', list3) 37 print(f'{list3 = }') 38 39# 1回目: ['test1'] 40# 2回目: ['test2'] 41# list3 = [1, 2, 3, 'test3']
投稿2025/08/28 02:37
編集2025/08/30 19:45総合スコア21449
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/08/28 03:00

0
既に解決済みなので御参考です。
実際に起こっていることを把握するために関数の規定値を(使う前に)確認するには .__defaults__
を用います。
Python
1def add_item(item, items=[]): 2 items.append(item) 3 return items 4 5print(add_item.__defaults__) 6# ([],) 7 8list1 = add_item("test1") 9print("1回目:", list1) 10# 1回目: ['test1'] 11 12print(add_item.__defaults__) 13# (['test1'],) 14 15list2 = add_item("test2") 16print("2回目:", list2) 17# 2回目: ['test1', 'test2']
ちなみに,「該当のソースコード」を test.py
に保存し Pylint を試したところ下記の出力が得られました。なにしろ Dangerous
ですから,特別な理由がない限りミュータブルオブジェクト(リスト,辞書など)は関数の引数の規定値に使わない方が良いでしょう。
なお,「pylint dangerous-default-value」を検索にかけると FAQ の回答と同様の内容も得られるようです。
Sh
1> pylint --disable=C,R test.py 2************* Module test 3test.py:1:0: W0102: Dangerous default value [] as argument (dangerous-default-value) 4 5------------------------------------------------------------------ 6Your code has been rated at 8.57/10 (previous run: 8.57/10, +0.00)
投稿2025/09/02 01:41
総合スコア507
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
別なトコにも書いたけど、これは殆どPythonのバグと言って良い挙動だ。
実際、通常のプログラミング言語だとigohas氏の想定通りの動きとなる。
JavaScript
1#!/usr/bin/env node 2 3function add_item(item, items=[]) { 4 items.push(item); 5 return items; 6} 7 8if (require.main === module) { 9 // 使用例 10 list1 = add_item("test1"); 11 console.log(`1回目: ['${list1}']`); // 期待値: ['test1'] 12 13 list2 = add_item("test2"); 14 console.log(`2回目: ['${list2}']`); // 期待値: ['test2'] 15}
Racket
1#!/usr/bin/env racket 2#lang racket/base 3 4(require (only-in srfi/1 append!)) 5 6(define add-item 7 (case-lambda 8 ((item items) (append! items `(,item))) 9 ((item) (add-item item '())))) 10 11(module+ main 12 ;; 使用例 13 (let ((list1 (add-item "test1"))) 14 (printf "1回目: ~a~%" list1) ; 期待値: (test1) 15 (let ((list2 (add-item "test2"))) 16 (printf "2回目: ~a~%" list2) ; 期待値: (test2) 17 )))
Ruby
1#!/usr/bin/env ruby 2 3def add_item(item, items=[]) 4 items.push(item) 5end 6 7if __FILE__ == $0 8 # 使用例 9 list1 = add_item("test1") 10 puts("1回目: #{list1}") # 期待値: ["test1"] 11 12 list2 = add_item("test2") 13 puts("2回目: #{list2}") # 期待値: ["test2"] 14end 15
Pythonが唯一バグ染みた挙動になってる。
確かにここに言い訳は書いてある。
ただし、問題は、一体誰がそんな挙動で得をするかなんだ。誰にも得なんざない。
よって、殆どバグなんだよ。
Pythonには言語仕様書がないんで、理論的根拠(Rationale)ってのがない。従って実装のマズさはドキュメンテーションでカバーするって事を良くやってるんだけど、ハッキリ言うと、実装を言い訳にするってのはサイテーなんだ(笑)。
良くジョークで出てくるけど「このバグは仕様です」って言うアレだな(笑)。
Pythonにはちょくちょく、こういった落とし穴があって、フツーのモダンなプログラミング言語じゃ思いも付かない挙動を起こす事があるんだ。
Tipsを書こう。そもそも基本的にはリストのメソッドは使ってはならない。
まぁ、語弊はあるんだけど、要は破壊的変更を伴うメソッドはおいそれとは使うなって事だ。破壊的変更、ってのはデータを直接書き換える事だ。append
メソッドは典型的な破壊的変更メソッドだ。
基本的に破壊的変更を伴うメソッドは最適化の為にある。要は本来、プログラムを書き始める時に使うモノじゃあないんだ。
Pythonにはプロファイラが同梱されていて、プログラムを書き終わった際に、プログラムのスピードを遅くしてる原因(ボトルネックと呼ぶ)を探る事が出来る。
んで、「プログラムを遅くしてる原因」がリスト操作に関する部分だった場合、それらを挿し替える為に破壊的変更メソッドが存在する、んだ。一般的に、確かに破壊的変更メソッドの方が色々と効率が良かったりする。ただし、破壊的変更はバグを産む原因にもなるんだ。
よって最初から使ってはならない。早すぎる最適化は諸悪の根源ってヤツだな。
この題意だと次のようにして書くのが正解だ。
Python
1#!/usr/bin/env python3 2 3def add_item(item, items=[]): 4 return items + [item] 5 6if __name__ == '__main__': 7 # 使用例 8 list1 = add_item('test1') 9 print(f'1回目: {list1}') # 期待値: ['test1'] 10 11 list2 = add_item('test2') 12 print(f'2回目: {list2}') # 期待値: ['test2] 13
Pythonも悪いが、そもそもappend
メソッドを使うのが間違ってるんだ。こんなん早々に使っちゃならない、ってのは先に書いた通りだ。安易な破壊的変更は一般にどんな挙動を引き起こすか分かったモンじゃないから、だ。
フツーにリスト同士の加算(ポリモーフだけど)を使えば、デフォルト引数の挙動がどうあれ、おかしな動作は避ける事が出来る。
投稿2025/08/28 08:55
編集2025/08/28 16:26総合スコア209
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/08/28 09:13
2025/08/28 11:01

0
pythonの関数定義のデフォルトの引数に指定されたオブジェクトは定義時に作られたオブジェクトそのものになり、全ての関数呼び出しで共有のものになります。特にリストや辞書(dict)は影響が出やすいです。
デフォルト引数に新たに生成したリストを使いたい場合、デフォルトでNoneにしておき、関数内で生成します。
python
1def add_item(item, items=None): 2 if items is None: 3 items = [] 4 items.append(item) 5 return items
投稿2025/08/28 01:21
編集2025/08/28 01:25総合スコア14610
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。