空のリストであるデフォルトのパラメーターを回避するためのpythonicの方法は何ですか?


117

空のリストであるデフォルトのパラメータがあるのは自然なことです。しかし、Pythonはこれらの状況で予期しない動作をします

たとえば、私が関数を持っている場合:

def my_func(working_list = []):
    working_list.append("a")
    print(working_list)

初めて呼び出されたときはデフォルトで機能しますが、その後に呼び出すと既存のリストが更新され(各呼び出しに「a」が1つずつ)、更新されたバージョンが出力されます。

それで、私が望む動作(各呼び出しの新しいリスト)を取得するためのpython的な方法は何ですか?


17
これが発生する理由に興味がある場合は、effbot.org / zone / default
Ryan Haining

セットでも同じ動作が発生しますが、バグとして表示するには少し複雑な例が必要です。
abeboparebop 2015年

リンクが無効になると、これは望ましい動作であることを明示的に指摘させてください。デフォルト変数は関数定義時に評価され(最初に呼び出されたときに発生します)、関数が呼び出されるたびに評価されません。したがって、変更可能なデフォルト引数を変更すると、その後の関数呼び出しでは変更されたオブジェクトのみを使用できます。
Moritz

回答:


150
def my_func(working_list=None):
    if working_list is None: 
        working_list = []

    working_list.append("a")
    print(working_list)

ドキュメントでNone、デフォルトとして使用し、関数の本体で明示的にテストする必要があると述べています。


1
if working_list == None:またはif working_list:??
John Mulder

2
醜いので気に入らなくても、これはpythonで行うための推奨される方法です。「working_listがNoneの場合」がベストプラクティスになると思います。
e-satis

21
この例で推奨される方法は、working_listがNoneの場合です。呼び出し元は、カスタムの追加で空のリストのようなオブジェクトを使用した可能性があります。
tzot 2008

5
Mohit Ranka:長さが0の場合、working_listがTrueでないことに注意してください。これにより、一貫性のない動作が発生します。関数が何らかの要素を含むリストを受け取った場合、呼び出し元はリストを更新し、リストが空の場合は、触れられません。
vincent

1
@PatrickT正しいツールはケースに依存します— varargs関数は、(オプションの)リスト引数を取る関数とは大きく異なります。あなたがそれらから選択しなければならない状況は、あなたが考えるよりも頻繁に現れることはありません。Varargsは、argカウントが変更された場合に最適ですが、コードがWRITTENの場合は修正されます。あなたの例のように。ランタイム変数の場合、またはf()リストを呼び出したい場合は、どちらを呼び出すf(*l)かが重要になります。さらに悪いことに、実装mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])はvarargsで成功します。それがはるかに良いですdef mate(birds=[], bees=[]):
FeRD

26

既存の回答では、求められている直接的な解決策がすでに提供されています。ただし、これは新しいPythonプログラマにとって非常に一般的な落とし穴であるため、Pythonがこのように動作する理由を説明を追加する価値があります。これは、「Hitchhikers Guide to Python」に「Mutable Default Arguments」としてうまくまとめられています: http:// docs .python-guide.org / en / latest / writing / gotchas /

引用: " Pythonのデフォルト引数は、関数が呼び出されるたびにではなく、関数が定義されるときに1回評価されます(たとえば、Rubyのように)。これは、変更可能なデフォルト引数を使用して変更すると、関数への今後のすべての呼び出しのためにそのオブジェクトを変更しました

それを実装するサンプルコード:

def foo(element, to=None):
    if to is None:
        to = []
    to.append(element)
    return to

13

この場合は重要ではありませんが、オブジェクトIDを使用してNoneをテストできます。

if working_list is None: working_list = []

ブール演算子またはPythonでの定義方法を利用することもできます。

working_list = working_list or []

ただし、呼び出し元がworking_listとして空のリスト(falseとしてカウントされます)を与え、関数が彼が与えたリストを変更することを期待している場合、これは予期しない動作をします。


10

関数の目的がとして渡されたパラメーターを変更することである場合はworking_list、HenryRの回答を参照してください(=なし、内部でなしを確認)。

しかし、引数を変更するつもりがなかった場合は、それをリストの開始点として使用するだけで、簡単にコピーできます。

def myFunc(starting_list = []):
    starting_list = list(starting_list)
    starting_list.append("a")
    print starting_list

(またはこの単純なケースprint starting_list + ["a"]では、ただのおもちゃの例だと思います)

一般に、Pythonでは引数の変更は悪いスタイルです。オブジェクトを変更することが完全に期待される唯一の関数は、オブジェクトのメソッドです。オプションの引数を変更することはさらにまれです—一部の呼び出しでのみ発生する副作用は本当に最高のインターフェイスですか?

  • 「出力引数」というCの習慣からそれを行う場合、それは完全に不要です-常に複数の値をタプルとして返すことができます。

  • 中間リストを作成せずに結果の長いリストを効率的に作成するためにこれを行う場合は、それをジェネレーターとして作成し、それをresult_list.extend(myFunc())呼び出すときに使用することを検討してください。このようにして、呼び出し規約は非常にクリーンなままです。

オプションの引数の変更頻繁に行われる1つのパターンは、再帰関数の非表示の「メモ」引数です。

def depth_first_walk_graph(graph, node, _visited=None):
    if _visited is None:
        _visited = set()  # create memo once in top-level call

    if node in _visited:
        return
    _visited.add(node)
    for neighbour in graph[node]:
        depth_first_walk_graph(graph, neighbour, _visited)

3

私はトピックから外れているかもしれませんが、可変数の引数を渡すだけの場合、Pythonの方法はタプル*argsまたは辞書を渡すことです**kargs。これらはオプションであり、構文よりも優れていますmyFunc([1, 2, 3])

タプルを渡したい場合:

def myFunc(arg1, *args):
  print args
  w = []
  w += args
  print w
>>>myFunc(1, 2, 3, 4, 5, 6, 7)
(2, 3, 4, 5, 6, 7)
[2, 3, 4, 5, 6, 7]

辞書を渡したい場合:

def myFunc(arg1, **kargs):
   print kargs
>>>myFunc(1, option1=2, option2=3)
{'option2' : 2, 'option1' : 3}

0

すでに提供されている適切な正解があります。たとえば、デフォルトの空のリストを使用してクラスを作成する場合に、より美しいと思う構文を別の構文で指定したいと思います。

class Node(object):
    def __init__(self, _id, val, parents=None, children=None):
        self.id = _id
        self.val = val
        self.parents = parents if parents is not None else []
        self.children = children if children is not None else []

このスニペットでは、if else演算子の構文を使用しています。特にコロンなどのない綺麗なワンライナーで、普通の英文のように読めるので特に気に入っています。:)

あなたの場合、あなたは書くことができます

def myFunc(working_list=None):
    working_list = [] if working_list is None else working_list
    working_list.append("a")
    print working_list

-3

UCSC拡張クラスを受講しました Python for programmer

これは正しい:def Fn(data = []):

a)呼び出しごとにデータリストが空になるようにすることをお勧めします。

b)呼び出しに引数を提供しない関数へのすべての呼び出しが空のリストをデータとして取得できるようにするための良いアイデアです。

c)データが文字列のリストである限り、合理的なアイデアです。

d)デフォルト[]はデータを蓄積し、その後の呼び出しでデフォルト[]が変わるため、これは悪い考えです。

回答:

d)デフォルト[]はデータを蓄積し、その後の呼び出しでデフォルト[]が変わるため、これは悪い考えです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.