Boş listeler olan varsayılan parametrelerden kaçınmanın pitonik yolu nedir?


118

Bazen boş bir liste olan varsayılan bir parametrenin olması doğal görünür. Yine de Python, bu durumlarda beklenmedik davranışlar verir .

Örneğin, bir işlevim varsa:

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

İlk çağrıldığında varsayılan olarak çalışacaktır, ancak bundan sonraki aramalar mevcut listeyi günceller (her aramada bir "a" ile) ve güncellenmiş sürümü yazdırır.

Öyleyse, arzu ettiğim davranışı elde etmenin pitonik yolu nedir (her aramada yeni bir liste)?


17
Bunun neden olduğu ile ilgilenen varsa, effbot.org/zone/default-values.htm adresini ziyaret
Ryan Haining

Aynı davranış setler için de geçerlidir, ancak bunun bir hata olarak görünmesi için biraz daha karmaşık bir örneğe ihtiyacınız vardır.
abeboparebop

Bağlantılar ölürken, bunun istenen davranış olduğunu açıkça belirtmeme izin verin. Varsayılan değişkenler, işlev tanımında değerlendirilir (ilk çağrıldığında gerçekleşir) ve işlev her çağrıldığında DEĞİLDİR. Sonuç olarak, değişebilir bir varsayılan bağımsız değişkeni değiştirirseniz, sonraki herhangi bir işlev çağrısı yalnızca değiştirilmiş nesneyi kullanabilir.
Moritz

Yanıtlar:


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

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

DokümanlarNone , varsayılan olarak kullanmanız ve bunu işlevin gövdesinde açıkça test etmeniz gerektiğini söylüyor .


1
Şunu söylemek daha mı iyi: if working_list == Yok: veya if working_list: ??
John Mulder

2
Çirkin olduğu için sevmesem bile, bunu python'da yapmanın tercih edilen yolu budur. En iyi uygulamanın "çalışma_ listesi Yok ise" olacağını söyleyebilirim.
e-satis

21
Bu örnekte tercih edilen yol şunu söylemektir: eğer çalışma_listesi Yok ise. Arayan, özel bir ek ile boş bir liste benzeri nesne kullanmış olabilir.
tzot

5
Mohit Ranka: uzunluğu 0 ise, çalışma_listesinin True olmadığına dikkat edin. Bu tutarsız bir davranışa yol açar: eğer işlevler içinde bazı elemanlar bulunan bir liste alırsa, arayanın listesi güncellenir ve liste boşsa, dokunulmayacak.
vincent

1
@PatrickT Doğru araç duruma bağlıdır - bir varargs işlevi, (isteğe bağlı) bir liste argümanı alan bir işlevden çok farklıdır. Aralarında seçim yapmanız gereken durumlar sandığınızdan daha az ortaya çıkar. Varargs, arg sayısı değiştiğinde harikadır, ancak kod YAZILI olduğunda sabittir. Örneğiniz gibi. Çalışma zamanı değişkeniyse veya f()bir listede aramak istiyorsanız f(*l), brüt olanı aramanız gerekir . Daha kötüsü, uygulama mate(['larch', 'finch', 'robin'], ['bumble', 'honey', 'queen'])varargs ile SUCK olacaktır. Öyleyse çok daha iyi def mate(birds=[], bees=[]):.
FeRD

26

Mevcut yanıtlar, istendiği gibi doğrudan çözümleri zaten sağladı. Bununla birlikte, bu yeni Python programcıları için çok yaygın bir tuzak olduğundan, python'un neden bu şekilde davrandığını açıklamaya değer, bu da " Otostopçuların Python Kılavuzu " nda " Değişken Varsayılan Argümanlar " olarak güzel bir şekilde özetlenmiştir : http: // docs .python-guide.org / tr / son / yazma / FRİKİKLERİNDEN /

Alıntı: " Python'un varsayılan argümanları fonksiyon tanımlandığında bir kez değerlendirilir, fonksiyon her çağrıldığında değil (örneğin Ruby'de olduğu gibi). Bu, değişken bir varsayılan argüman kullanır ve onu değiştirirseniz, bu nesneyi, işlevin gelecekteki tüm çağrıları için de değiştirdi "

Uygulamak için örnek kod:

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

13

Bu durumda önemli değil, ancak Yok'u test etmek için nesne kimliğini kullanabilirsiniz:

if working_list is None: working_list = []

Ayrıca boole operatörünün veya python'da nasıl tanımlandığından da yararlanabilirsiniz:

working_list = working_list or []

Yine de, arayan kişi size çalışma listesi olarak boş bir liste (yanlış sayılır) verirse ve işlevinizin verdiği listeyi değiştirmesini beklerse bu beklenmedik şekilde davranacaktır.


10

İşlevin amacı, geçirilen parametreyi değiştirmektir , working_listHenryR'nin cevabına bakın (= Yok, içinde Yok'u kontrol edin).

Ancak argümanı mutasyona uğratmak istemediyseniz, onu bir liste için başlangıç ​​noktası olarak kullanın, basitçe kopyalayabilirsiniz:

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

(ya da bu basit durumda, print starting_list + ["a"]ama sanırım bu sadece bir oyuncak örneğiydi)

Genel olarak, argümanlarınızı değiştirmek Python'da kötü bir tarzdır. Bir nesneyi tamamen değiştirmesi beklenen işlevler, nesnenin yöntemleridir. İsteğe bağlı bir argümanı değiştirmek daha da nadirdir - yalnızca bazı çağrılarda meydana gelen bir yan etki gerçekten en iyi arayüz mü?

  • Bunu "çıktı bağımsız değişkenleri" nin C alışkanlığından yaparsanız, bu tamamen gereksizdir - her zaman birden çok değeri bir demet olarak döndürebilirsiniz.

  • Ara listeler oluşturmadan uzun bir sonuç listesini verimli bir şekilde oluşturmak için bunu yaparsanız, bunu bir oluşturucu olarak yazmayı ve result_list.extend(myFunc())aradığınızda kullanmayı düşünün. Bu şekilde arama kurallarınız çok temiz kalır.

İsteğe bağlı arg mutasyona Bir desen olan sık yapılan özyinelemeli fonksiyonlarda bir gizli "not" arg geçerli:

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

Konu dışı olabilirim, ancak sadece değişken sayıda argüman iletmek istiyorsanız, pitonik yolun bir demet *argsveya sözlüğü iletmek olduğunu unutmayın **kargs. Bunlar isteğe bağlıdır ve sözdiziminden daha iyidir myFunc([1, 2, 3]).

Bir demeti geçmek istiyorsanız:

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]

Bir sözlüğü iletmek istiyorsanız:

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

0

Zaten verilen iyi ve doğru cevaplar var. Sadece yapmak istediğiniz şeyi yazmak için başka bir sözdizimi vermek istedim, örneğin varsayılan boş listelerle bir sınıf oluşturmak istediğinizde daha güzel buluyorum:

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

Bu pasaj, if else operatörü sözdizimini kullanır. Özellikle, iki nokta üst üste vb. İçermeyen düzgün, küçük bir tek satırlık olduğu ve neredeyse normal bir İngilizce cümle gibi okuduğu için hoşuma gidiyor. :)

Senin durumunda yazabilirsin

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

-3

UCSC uzatma sınıfını aldım Python for programmer

Hangisi doğrudur: def Fn (data = []):

a), veri listelerinizin her aramada boş başlaması için iyi bir fikirdir.

b), çağrıda herhangi bir argüman sağlamayan işleve yapılan tüm çağrıların boş listeyi veri olarak alması için iyi bir fikirdir.

c) verileriniz dizelerden oluşan bir liste olduğu sürece makul bir fikirdir.

d) kötü bir fikirdir çünkü varsayılan [] veri biriktirecek ve varsayılan [] sonraki çağrılarla değişecektir.

Cevap:

d) kötü bir fikirdir çünkü varsayılan [] veri biriktirecek ve varsayılan [] sonraki çağrılarla değişecektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.