質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

3回答

2002閲覧

デフォルト引数値を設定しているのに、それがデフォルトではないように働いていることについて

gunmed

総合スコア55

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

1グッド

2クリップ

投稿2019/02/13 05:00

編集2019/02/14 05:51

関数の引数について勉強している初学者です。

引数には位置引数、キーワード引数、デフォルト引数などがあると学びました。
そしてデフォルト引数値で以下のような2つのコードの違いを検討していたのですが、いまいち違いがわからないので質問します。

def test(arg, result=[]): result.append(arg) print(result) test('a') test('b') print(test('a'))

結果は以下です。

['a'] ['a', 'b']

後者のコードは以下です。

def test(arg): result = [] result.append(arg) print(result) test('a') test('b') print(test('a'))

結果は以下です。

['a'] ['b']

本来目的は後者の方のように、aとbという引数がそれぞれ一つずつリストに加えられた状態で出力される
というものでした。
前者のコードではtestを呼び出した際にaで呼びたした時はresultは空であるが、bで呼び出した時には前回呼び出した要素が残っているので、['a', 'b']と表示されると説明がありましたが、そこがいまいち理解できません。

自分の中では後者のコードではresultは関数内で定義されたローカル変数です。関数内というローカルなスコープで'a'を引数としてresultのリストに追加しても、それはローカル内の出来事なので、次のtest('b')には影響せず、結果のように別々に表示されると考えました。

前者の方はresultの方もデフォルト引数値として空のリスト[]を与えています。だから、test('a')の後にtest('b')をやっても結局デフォルトでresult=[]となっているから、test('b')を実行した際にはresult=[]がデフォルト引数値となるので、test('a')の影響は受けないと思うのですが、test('b')の結果は['a', 'b']となっています。

以上のことについての回答や、自分の考えの間違いの指摘を頂けたら幸いです。

mac 10.14.1
visual studio code

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

__defaults__と__kwdefaults__の使い方について、質問の返信スペースではわかりにくくなるため、ここに質問内容を記載します。

以下のようなコードを作成し、使い方について自分なりに考えてみました。

def func(args=[21]): print(args) func.__defaults__[0].append(42) func() #>>>[21, 42] def kinou(args=['mail', 'address']): print(args) kinou.__defaults__[0].remove('mail') kinou() #>>>['address'] def lite(args='slim'): print(args) lite() lite.__defaults__ = 'spam', lite() #>>>slim # spam

関数が呼び出された際不足している実引数を補う際に利用されるとのことだったので、色々な値を動かしてみました。

LouiS0616さんが提示してくれたコードは以下でした。

>>> def func(args=[]): ... print(args) ... >>> func() [] >>> >>> func.__defaults__[0].append(42) >>> func() [42] >>> >>> func.__defaults__ = 'spam', >>> func() spam

まず、自分が悩んだのは[0].append(42)の[0]でした。この[0]がどういった意味で置かれているのかがわからずまずは[]という空のリストにしてみるとエラーがでしまいました。
そして、たどり着いたのが自分の作ったコードのfunc関数でした。[21, 42]が結果としてでてくることから、[0]は42という実引数を入れるのに必要なもので、args=[21]を表しているのかなと思いました。しかし、[0]とするのが定義上の決まりなのかどうかがわかりませんでした。

つぎに、たどり着いたのが、kinou関数でした。appendできるならremoveもできるのかなと思い実行するとできました。やはり、[0]はargsのリストを表しているということが確信できました。

そして、func.defaults = 'spam',について考えました。おそらくargs=[]の=以降を変更するのではと考え、args='slim'として行ったところ、spamに変更されていたので、=とするとデフォルト値を変更できるのではという結論に自分の中で至りました。
また、'spam',の,が気になったので、'spam'と変更してみると、

TypeError: defaults must be set to a tuple object

と表示されました。一応公式では__defaults__は『デフォルト値を持つ引数に対するデフォルト値が収められたタプル』となっていたので、タプルでは('aaa',)というように,が必要だったのを思い出すと=でデフォルト値を変更する時はタプルとしての操作をする(先例のリストの追加をするときの[0]とは別)のかなと納得しましたが、そういう理解でいいのか確証はありません。

また、__kwdefaults__は公式によると、キーワード専用パラメータのデフォルト値を含む辞書となっていたので、以下のようなコードを書いて実験してみました。

def moji(a, *, b='ow'): print(a,b,sep='') moji('r') #>>>row moji.__kwdefaults__ ='ing' moji('r') #>>>TypeError: __kwdefaults__ must be set to a dict object

となってしまいました。やはり、__kwdefaults__辞書のようです。キーワード専用パラメータのデフォルト値は結果として変更できませんでした。
また、辞書のような形式で変更してみたらどうだろうと思い、先ほどのリストのように以下のようにコードを作りました。

moji.__kwdefaults__ {0}['b']='ing' moji('r') #>>>moji.__kwdefaults__ {None:None}['b']='ing' # ^

func.defaults[0].append(42)のような形式で、辞書を値のない辞書として、0のかわりにNoneを使用してみましたが、エラーでした。

ネットや本を調べても詳細が見つからないので、上記の[0]の意味について、=での変更のについて、__kwdefaults__の使い方についての主に3つについて教えていただけたら幸いです。よろしくお願いします。

tachikoma👍を押しています

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

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

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

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

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

guest

回答3

0

ベストアンサー

関数の呼び出しによって、デフォルトの値に対する新しいオブジェクトが作られるのだと予想しがちです。実はそうなりません。デフォルト値は、関数が定義されたときに一度だけ生成されます。この例の辞書のように、そのオブジェクトが変更されたとき、その後の関数の呼び出しは変更後のオブジェクトを参照します。

引用元: Python よくある質問 » なぜオブジェクト間でデフォルト値が共有されるのですか?


なお、デフォルト値は次のように確認できます。

Python

1>>> def func(arg='spam', *, kwarg='ham'): 2... pass 3... 4>>> func.__defaults__ 5('spam',) 6>>> func.__kwdefaults__ 7{'kwarg': 'ham'}

このことからも、デフォルト値が関数オブジェクトに所有されていることが分かります。
なお、次のような不可解な操作も可能です。

Python

1>>> def func(args=[]): 2... print(args) 3... 4>>> func() 5[] 6>>> 7>>> func.__defaults__[0].append(42) 8>>> func() 9[42] 10>>> 11>>> func.__defaults__ = 'spam', 12>>> func() 13spam

質問追記を受けて

関数オブジェクトは呼び出された際、不足する引数にデフォルト値が定められていないか調査します。
そのときにチェックされる帳簿が__defaults__及び__kwdefaults__です。

自分が悩んだのは[0].append(42)の[0]でした。

__defaults__がデフォルト値のタプルだからです。

Python

1>>> def func(arg1=[], arg2=[]): 2... print(arg1, arg2) 3... 4>>> func.__defaults__ 5([], []) 6>>> type(func.__defaults__) 7<class 'tuple'> 8>>> 9>>> func.__defaults__[0] 10[] 11>>> type(func.__defaults__[0]) 12<class 'list'> 13>>> 14>>> func.__defaults__[0].append(42) 15>>> func() 16[42] [] 17>>> 18>>> func.__defaults__[1].append(6) 19>>> func() 20[42] [6]

func.defaults = 'spam',について考えました。おそらくargs=[]の=以降を変更するのではと考え、args='slim'として行ったところ、spamに変更されていたので、=とするとデフォルト値を変更できるのではという結論に自分の中で至りました。

その考えで問題無いように思います。
ただし関数オブジェクトの属性を書き換えているだけなので、決して特殊な文法ではありませんが。

タプルでは('aaa',)というように,が必要だったのを思い出すと=でデフォルト値を変更する時はタプルとしての操作をする(先例のリストの追加をするときの[0]とは別)のかな

単一要素のタプルのリテラルを記述するときは、
文法の都合上最後にカンマ(俗称ケツカンマ)を付けなければなりません。

カンマを付けないと、計算の順序を操作する括弧と見分けが付かないからです。

func.defaults[0].append(42)のような形式で、辞書を値のない辞書として、0のかわりにNoneを使用してみましたが、エラーでした。

やるならこんな感じでしょうか。

Python

1>>> def moji(a, *, b='ow'): 2... print(a, b, sep='') 3... 4>>> moji.__kwdefaults__ 5{'b': 'ow'} 6>>> moji.__kwdefaults__['b'] = 'ing' 7>>> 8>>> moji('r') 9ring

投稿2019/02/13 05:09

編集2019/02/14 06:34
LouiS0616

総合スコア35658

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

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

gunmed

2019/02/13 11:19

回答ありがとうございます。 質問した内容についてはしっかりと理解できました。 多くの人が陥りやすいミスだったのですね。勉強になりました。 ちなみに、LouiS0616さんがあげてくれた例で、__defaults__とは一体なんなのかが気になってしまい、色々と調べたのですが、自分の解釈がどこまで正しいかわからないので、__defaults__は提示していただいたコードの中で、どういう働きをしているのか教えていただけると幸いです。 自分で調べたところだと、__で囲まれたものは特殊メソッド(特殊属性??)といい、自作のクラスに振る舞いを付け加えるとあったのですが、今回のものは違う働きをしているように思えました。さらに調べると、__defaults__は『デフォルト値を持つ引数に対するデフォルト値が収められたタプル』という定義が公式にありました。確かに、'spam',が表示されるので、__default__はメソッドで定義されているデフォルト値を戻り値にする働きがあるものなのかなと考えました。でも、'spam','ham'と表示されないのが疑問に思いました。,*,で何か変化が起こっていて、デフォルト値として表示されないのかなとも考えたのですが、混乱してきたので、質問させていただきました。 お手すきでしたら、返信よろしくお願いします。
LouiS0616

2019/02/13 11:58 編集

> __で囲まれたものは特殊メソッド(特殊属性??)といい、自作のクラスに振る舞いを付け加えるとあった 物凄くざっくり言ってしまうと、双方に共通する特徴は『内部的に利用される』ことです。 例えば a[b] は、特殊メソッドの呼び出し a.__getitem__(b) に置き換えられます。 普通はやらないですが、コード上で明示的に特殊メソッドを呼び出すこともできます。 特殊属性 __defaults__ 及び __kwdefaults__ は、 関数が呼び出された際不足している実引数を補う際に利用されます。 逆に言うと、内部的あるいは暗黙的に利用されること以外は通常のメソッドや属性と変わりないので、 個人的には『特殊』と冠することに若干の疑問を抱いていたりします。 --- > *,で何か変化が起こっていて 関数定義の際、*, 以降に置いた仮引数は『キーワード専用パラメータ』と呼ばれるものになります。 例えば def func1(a, b) は func1(1, 2) でも func1(1, b=2) でも呼び出せますが、 一方 def func2(a, *, b) は func2(1, 2) と呼ぶことができなくなるのです。 キーワード専用パラメータにはその性質上デフォルト値が用意されることが多いですが、必ずそのように実装しなければいけないわけではありません。 --- > __defaults__は [中略] 'spam','ham'と表示されないのが疑問 仮引数kwargsはこの場合キーワード専用パラメータですので、 そのデフォルト値は __defaults__ ではなく __kwdefaults__ に保持されます。
gunmed

2019/02/14 05:54

返信ありがとうございます。 かなり理解できたのですが、説明で作っていただいたコードの不可解な操作について、__defaults__と__kwdefaults__について色々調べたのですが、なかなか難しかったので、さらなる質問を編集して付け加えたので、それについて回答していただけるととても勉強になります。分量が長くなってしまい申し訳ありません。
LouiS0616

2019/02/14 06:47

追記しました。 --- 私から話を振っておいて難ですが、__defaults__ と __kwdefaults__ は影で良しなに働いてくれるものなので、あまり詳細を気にしても得るものは少ないと思います。 増してや __defaults__ を書き換えることは、よほど特殊なケースです。 もちろんこのような挙動を知っていて損をすることは無いですし、学ぶものがないわけではありませんが、学習として時間対効果があまり良くないということはいちおうお伝えしておきます。 好奇心に水を差すようなお節介ですみません。
gunmed

2019/02/14 08:13

回答ありがとうございます。 説明の内容、とてもよくわかりました。 特殊なケースなのですね。なんか便利そうなので、よく使うものなのかなと勝手に思ってました。 そもそも特殊メソッドを学んだことがなかったので、色々と気になって、つい深入りしてしまいました。 時間がたくさんあるわけではないので、この辺で次の学習に進もうと思います。 アドバイスありがとうございます。
hayataka2049

2019/02/14 09:51

私など、デフォルト値を特殊属性から触ったり、書き換えられることをこれまで全く知りませんでした。もちろんそれで困ったことはないです。
LouiS0616

2019/02/14 10:04 編集

とにかく使い道が分からず、以前『__defaults__が書き換え可能で、しかもそれが公式リファレンスに明記してある理由は何ですか?』と質問を立てようと思ったことがあります。 あまりに漠然とした内容なので、投稿はしませんでしたが。 私自身の中では『書き換え不可能にする積極的理由が無いから』と結論付けました。
guest

0

すでにデフォルトではないように働く理由についての回答は出揃っていると思いますが、一点だけ補足させていただくと、質問文に記されているような動作にしたい場合はこう書きます。

python

1def test(arg, result=None): 2 if result is None: 3 result = [] 4 result.append(arg) 5 print(result)

投稿2019/02/13 08:13

編集2019/02/13 08:48
hayataka2049

総合スコア30933

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

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

gunmed

2019/02/13 11:32

回答ありがとうございます。 なるほど、シンプルなコードですね。 自分の理解が甘いせいか、以下のように自分で解釈してしまい、このコードが正しく解釈できません、、、。 自分の例を使うと test('a')とすると、関数生成時の最初のみにデフォルト値を使うので、もちろんresultはNoneであることから、result is NoneはTrueとなり、result = []が作られて、aが追加される。 その次にtest('b)とやると、test('a')でresult = [ 'a' ]となっているので、result is NoneはFalseとなり、result =[]にリセットされず、result.append('b')を実行し、result = [ 'a' ]に'b'が追加され、['a', 'b']となる。 のような解釈になってしまいます。 どこか間違って解釈していると思うのですが、どうでしょうか? お手すきの際に、返信していただけたら幸いです。
hayataka2049

2019/02/13 11:43 編集

ローカル変数のresultと仮引数のresultは厳密に言えば別物です。便宜的にデフォルト値を持つ仮引数のスコープがローカルスコープの外側にあるとみなせば良いでしょう。 その考え方で行くと、result = []は「仮引数result」はそのままにして「ローカル変数result」を作ります。仮引数resultは(LouiS0616さんの回答にあるように不可解なことをしない限りは)実行中ずっと関数定義時のときに代入された値を保持します。 ローカル変数resultは呼び出しが終われば消え、仮引数resultはそのまま残る(これはもうそういう仕組みとしか言えないが)ので、resultを実引数として渡さない限りは毎回result =[]にリセットされるような動作になります。
gunmed

2019/02/14 03:45

回答ありがとうございます。 しっかり理解できました。確かに、デフォルトではresult = Noneとなっていて、result = []はローカルで定義されていて、デフォルト値をローカルスコープの外側とするとすべての流れがつながりました。 ありがとうございました。
hayataka2049

2019/02/14 04:44

あくまでも便宜的にそう説明しただけなので、そんな感じに振る舞うという程度に思っていただければ
gunmed

2019/02/14 08:30

はい、わかりました。 この件を通して、色々と勉強になりました。 ありがとうございました。
guest

0

前者の方はresultの方もデフォルト引数値として空のリスト[]を与えています。

Pythonの場合、デフォルト引数は関数生成時に初期化されるので、何回実行しても同じリストを使います(リファレンス)。結果、実行のたびに要素が増えていくこととなります。

投稿2019/02/13 05:06

maisumakun

総合スコア145121

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

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

gunmed

2019/02/13 11:21

回答ありがとうございます。 関数生成時のみということですね。その説明を見落としてました。 今後気をつけて行きたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問