Apa cara pythonic untuk menghindari parameter default yang berupa daftar kosong?

117

Terkadang wajar jika memiliki parameter default yang berupa daftar kosong. Namun Python memberikan perilaku yang tidak terduga dalam situasi ini .

Jika misalnya, saya punya fungsi:

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

Pertama kali disebut default akan berfungsi, tetapi panggilan setelah itu akan memperbarui daftar yang ada (dengan satu "a" untuk setiap panggilan) dan mencetak versi yang diperbarui.

Jadi, apa cara pythonic untuk mendapatkan perilaku yang saya inginkan (daftar baru di setiap panggilan)?

John Mulder
sumber
17
jika ada yang tertarik dengan mengapa ini terjadi, lihat effbot.org/zone/default-values.htm
Ryan Haining
Perilaku yang sama terjadi untuk set, meskipun Anda memerlukan contoh yang sedikit lebih rumit agar muncul sebagai bug.
abeboparebop
Saat tautan mati, izinkan saya secara eksplisit menunjukkan bahwa ini adalah perilaku yang diinginkan. Variabel default dievaluasi pada definisi fungsi (yang terjadi saat pertama kali dipanggil), dan TIDAK setiap kali fungsi dipanggil. Akibatnya, jika Anda memutasi argumen default yang bisa berubah, pemanggilan fungsi berikutnya hanya dapat menggunakan objek yang dimutasi.
Moritz

Jawaban:

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

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

Dokumen tersebut mengatakan Anda harus menggunakan Nonesebagai default dan secara eksplisit mengujinya di badan fungsi.

HenryR
sumber
1
Apakah lebih baik mengatakan: if working_list == Tidak ada: atau if working_list: ??
John Mulder
2
Ini adalah cara yang disukai untuk melakukannya dengan python, meskipun saya tidak menyukainya karena jelek. Saya akan mengatakan praktik terbaik adalah "jika working_list is None".
e-satis
21
Cara yang disukai dalam contoh ini adalah dengan mengatakan: if working_list is None. Penelepon mungkin telah menggunakan objek seperti daftar kosong dengan tambahan kustom.
tzot
5
Mohit Ranka: berhati-hatilah bahwa tidak working_list adalah True jika panjangnya 0. Hal ini mengarah pada perilaku yang tidak konsisten: jika fungsi menerima daftar dengan beberapa elemen di dalamnya, pemanggilnya akan memperbarui daftarnya, dan jika daftar kosong, itu tidak akan disentuh.
vincent
1
@PatrickT Alat yang tepat bergantung pada kasus - fungsi varargs sangat berbeda dari yang menggunakan argumen daftar (opsional). Situasi di mana Anda harus memilih di antara mereka muncul lebih jarang dari yang Anda kira. Varargs sangat bagus ketika jumlah argumen berubah, tetapi diperbaiki ketika kode TERTULIS. Seperti teladan Anda. Jika itu runtime-variable, atau Anda ingin memanggil f()daftar, Anda harus memanggil f(*l)mana yang kotor. Lebih buruk lagi, implementasi mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])akan SUCK w / varargs. Jauh lebih baik jika itu def mate(birds=[], bees=[]):.
FeRD
26

Jawaban yang ada telah memberikan solusi langsung seperti yang diminta. Namun, karena ini adalah perangkap yang sangat umum untuk programmer Python baru, ada baiknya menambahkan penjelasan mengapa python berperilaku seperti ini, yang dirangkum dengan baik dalam " Panduan Penumpang untuk Python " sebagai " Argumen Default yang Dapat Diubah ": http: // docs .python-guide.org / id / latest / writing / gotchas /

Kutipan: " Argumen default Python dievaluasi satu kali ketika fungsi didefinisikan, tidak setiap kali fungsi dipanggil (seperti yang di katakan, Ruby). Ini berarti bahwa jika Anda menggunakan argumen default yang bisa berubah dan memutasinya, Anda akan dan memiliki mutasi objek itu untuk semua panggilan mendatang ke fungsi juga "

Kode contoh untuk menerapkannya:

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

Tidak penting dalam kasus ini, tetapi Anda dapat menggunakan identitas objek untuk menguji None:

if working_list is None: working_list = []

Anda juga dapat memanfaatkan cara operator boolean atau didefinisikan dengan python:

working_list = working_list or []

Meskipun ini akan berperilaku tidak terduga jika pemanggil memberi Anda daftar kosong (yang dihitung sebagai false) sebagai working_list dan mengharapkan fungsi Anda untuk mengubah daftar yang dia berikan.

bendin
sumber
10

Jika maksud dari fungsi ini adalah untuk mengubah parameter yang diteruskan sebagai working_list, lihat jawaban HenryR (= Tidak ada, periksa Tidak ada di dalam).

Tetapi jika Anda tidak bermaksud untuk mengubah argumen, gunakan saja sebagai titik awal untuk daftar, Anda cukup menyalinnya:

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

(atau dalam kasus sederhana ini, print starting_list + ["a"]tapi saya rasa itu hanya contoh mainan)

Secara umum, mutasi argumen Anda adalah gaya yang buruk di Python. Satu-satunya fungsi yang sepenuhnya diharapkan untuk mengubah suatu objek adalah metode objek tersebut. Bahkan lebih jarang untuk mengubah argumen opsional - apakah efek samping yang terjadi hanya dalam beberapa panggilan benar-benar merupakan antarmuka terbaik?

  • Jika Anda melakukannya dari kebiasaan C "argumen keluaran", itu sama sekali tidak perlu - Anda selalu dapat mengembalikan beberapa nilai sebagai tupel.

  • Jika Anda melakukan ini untuk membuat daftar hasil yang panjang secara efisien tanpa membuat daftar perantara, pertimbangkan untuk menulisnya sebagai generator dan menggunakannya result_list.extend(myFunc())saat Anda memanggilnya. Dengan cara ini, konvensi panggilan Anda tetap sangat bersih.

Salah satu pola di mana bermutasi arg opsional yang sering dilakukan adalah tersembunyi "memo" arg dalam fungsi rekursif:

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)
Beni Cherniavsky-Paskin
sumber
3

Saya mungkin di luar topik, tetapi ingat bahwa jika Anda hanya ingin memberikan sejumlah variabel argumen, cara pythonic adalah dengan mengirimkan tupel *argsatau kamus **kargs. Ini opsional dan lebih baik daripada sintaks myFunc([1, 2, 3]).

Jika Anda ingin melewatkan tupel:

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]

Jika Anda ingin lulus kamus:

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

Sudah ada jawaban yang baik dan benar yang diberikan. Saya hanya ingin memberikan sintaks lain untuk menulis apa yang ingin Anda lakukan yang menurut saya lebih indah ketika Anda misalnya ingin membuat kelas dengan daftar kosong default:

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 []

Cuplikan ini menggunakan sintaks operator if else. Saya menyukainya terutama karena ini merupakan satu baris kecil yang rapi tanpa titik dua, dll. Terlibat dan hampir terbaca seperti kalimat bahasa Inggris normal. :)

Dalam kasus Anda, Anda bisa menulis

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

Saya mengambil kelas ekstensi UCSC Python for programmer

Yang benar dari: def Fn (data = []):

a) adalah ide yang bagus agar daftar data Anda mulai kosong pada setiap panggilan.

b) adalah ide yang bagus sehingga semua panggilan ke fungsi yang tidak memberikan argumen apa pun pada panggilan tersebut akan mendapatkan daftar kosong sebagai data.

c) adalah ide yang masuk akal selama data Anda berupa daftar string.

d) adalah ide yang buruk karena default [] akan mengumpulkan data dan default [] akan berubah dengan panggilan berikutnya.

Menjawab:

d) adalah ide yang buruk karena default [] akan mengumpulkan data dan default [] akan berubah dengan panggilan berikutnya.

Peter Chen
sumber