質問するログイン新規登録
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

5回答

1183閲覧

Pythonでリストを出力する関数が意図通りに動かない

igohas

総合スコア39

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

2クリップ

投稿2025/08/28 01:09

1

2

実現したいこと

関数を呼び出すたびに新しいリストが作成される関数を作成したいです。
前回の関数呼び出しの結果が残らないようにしたいです。

前提

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

FKM👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

melian

2025/08/28 06:24 編集

list1 = add_item("test1") list2 = add_item("test2") list3 = add_item("test3") list3 = add_item("test4", list3) # もしくは # add_item("test4", list3) というのは、 list1 = ["test1"] list2 = ["test2"] list3 = ["test3"] list3.append("test4") と同義なので、関数にする意義がよく判りませんでした。例えば、関数のインタフェースを統一したい、関数/メソッドのデフォルトパラメータの使い方を学習したいなどの理由があるのでしょうか?
igohas

2025/08/28 10:40

理由は2つあり、1つは学習のために使用したいという思惑です! もう1つは、いずれこの関数を拡張して処理を追加しようと考えており、そのために先に分けて置きたかったためです!
guest

回答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
int32_t

総合スコア22002

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

igohas

2025/08/28 10:41

公式の引用ありがとうございます! 理解できました!
guest

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
melian

総合スコア21449

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TakaiY

2025/08/28 03:00

melianさん、いつも勉強になります。 「こんな方法もあるよ」という紹介とは思いますが、「+」(__add__メソッド)を使えばそこで新規オブジェクトが作られるので望む結果は得られますよ、というのは裏の裏を取るようで、あまりよい実装には思えません。
melian

2025/08/28 03:46

一応、「新しいリストが作成される関数」とのことなのでadd_item()の第2パラメータにリストを渡す場合には、そのリストの内容は変更しない、という意味に取りました。(質問者であるigohasさんとしては「第2パラメータを省略した場合にのみ新規にリストを作成する」という意図かもしれません) あまりよい実装ではないというのはその通りです。他にもリストのリストなどが対象の場合は shallow copy が発生しますので deep copy が必要になるでしょう。
igohas

2025/08/28 10:45

質問の意図としては新規リストに入れるだけで十分なのですが、いずれは既存リストに追加、等をする可能性を考えておりもしかしたらこちらの実装にしたいかもしれません。 第2パラメータ、など少し分からない部分もあり恐縮ですが、なぜこれが良く無い(?)実装なのか教えていただくことは可能でしょうか? (質問の意図とは少し違ったためベストアンサーにせずで本当にすみませんが…)
melian

2025/08/28 11:25

> なぜこれが良く無い(?)実装なのか教えていただくことは可能でしょうか? 使用メモリ量の観点からすれば新たにリストを生成することになりますので、その分のメモリを消費することになります。例えば整数のリストで要素数が1000万個の巨大リストに1個の整数を追加する場合では約80MBのメモリが新たに使用されることになります。(list.append()は破壊的(destructive)な操作なので数バイト程度です)
igohas

2025/09/01 07:19

なるほどです 理解できました。 ありがとうございます!!
guest

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

little_street

総合スコア507

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

igohas

2025/09/02 06:09

参考になります、解決後にも関わらず回答ありがとうございます!! となると他の人が言うように関数化させないことの方が良いのですかね…
little_street

2025/09/02 06:26

別の目的もあるなら関数化は特に問題ではなく,関数化する際に「ミュータブルオブジェクトを引数の規定値に設定すること」は避けた方が良いという意味です。
igohas

2025/09/05 04:24

理解しました。 補足していただき、非常に参考になりました。 ありがとうございます!
guest

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
cametan

総合スコア209

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

melian

2025/08/28 09:13

ここですが、順序が逆になっています。 return [item] + items => return items + [item] > 一体誰がそんな挙動で得をするかなんだ。誰にも得なんざない。 これはその通りですね。
cametan

2025/08/28 09:15

OOPs。ホントだ。 ごめん、Lispでconsする際の手癖で・・・(言い訳・苦笑)。 修正しときます。
igohas

2025/08/28 10:43

他の言語では大丈夫なのですか… 言語特性ってやつですかね(無知でしたらすみません) 詳しい解説ありがとうございます!!
TakaiY

2025/08/28 11:01

僕もlisperなので、immutableの世界がすっきりしていることはわかるのですが、広く布教するのはむずかしいかなと思いますので、うまくつきあっていくのがいいのではないかと。
cametan

2025/08/28 11:21

>> igohas氏 > 他の言語では大丈夫なのですか… うん。 他の言語と比較すれば、明らかにPythonは「おかしな挙動を是」としてますね。 > 言語特性ってやつですかね(無知でしたらすみません) いや、本文にも書いたけど、殆ど「バグ」です(笑)。 元々、Pythonってかつてライバル視されてたRubyみたいな「明確な設計目標があって」しっかりとデザインされた言語、じゃないんですよ。割にこう、その場凌ぎで色々改変されてきたような・・・・・・。 だから言語設計の根幹がゆらいでて、プログラミング初心者にはオススメはしないんだけど、単純に比較するとJavaScriptにも劣る、というような・・・(苦笑)。 もっともJavaScriptはJavaScriptでデータ型の変換とかでおかしなトコはあるんだけど。ただ、噂だと、「会社(旧・NetScape 原・Mozzila)に"10日間で開発しろ"」とか無茶ぶりされた割にはしっかりと設計はされてます(笑)。Python以上。 元々、Pythonってグルー言語なのね。例えばC言語とかFortranで書かれたプログラムとかのフロントエンドになる事が重要な目的になってる、っつーか。他の言語みたいに、「ワイ単独で速度を稼ぐでぇ~」みたいな風になっていない。Python自体で完結したプログラムを書く、ってのは元々意図してないし、現在でもそういう部分は大きい。 このデフォルト引数の問題は、ひょっとしたら「外部プログラムと連携を上手く取る」為の副産物かもしんない。良く知らんけど。 いずれにせよ、これは「落とし穴」で、「直す気がない」ってのは改変したら何らかの影響がある、って事でしょう。じゃなかったらPython2.x->Python3.x変更時に「直ってる」筈なのね。この時期に大幅に「後方互換性」は捨ててるからさ。 >> TakaiY氏 > 僕もlisper おお、僕は別にLisperではないですが、Lispやる人がいるのは嬉しいですね。 > immutableの世界がすっきりしていることはわかる Schemerかしらん? CLerは反面、あまりimutableな世界にキョーミがないみたいなんで・・・(笑)。 (彼らはとにかくマクロ・マクロな人たちなんで・笑) > 広く布教するのはむずかしいかなと思います うん。Pythonなんか見てると、初期はさておき、「関数型を良く知らない人」が多くなっちゃってぶっちゃけ「わやくちゃ」です(笑)。 2,000年代はそうでもなかったんですけどね~。Pythonは「関数型を良く知ってる人」向けの尖った言語だった。 昨今は「Pythonic」とか言う標語が出てきてて「喧嘩売ってんのか」とか思うようなコーディングが主流になってて閉口してます(笑)。 頑張って布教していきましょう(謎 > うまくつきあっていくのがいいのではないかと ただ、ホント、この「デフォルト引数」はバグ染みてますよ。言い訳もおかしな言い訳だし(笑)。 メモ化するならfunctools.@cacheがあるし、用途的にいうとデフォルト引数が、例示の通り「役に立つ」なんつーのは5%に満たないんじゃないかしらん。 この辺、やっぱANSI Common LispやSchemeと違って「理論的根拠に乏しい」と言わざるを得ないかな、とか個人的には思っています。
guest

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
TakaiY

総合スコア14610

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.30%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問