Częstym błędem w Pythonie jest ustawianie modyfikowalnego obiektu jako domyślnej wartości argumentu w funkcji. Oto przykład zaczerpnięty z tego wspaniałego artykułu Davida Goodgera :
>>> def bad_append(new_item, a_list=[]):
a_list.append(new_item)
return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']
Wyjaśnienie, dlaczego tak się dzieje, znajduje się tutaj .
A teraz moje pytanie: czy istnieje dobry przypadek użycia dla tej składni?
Chodzi mi o to, że jeśli każdy, kto go napotka, popełnia ten sam błąd, debuguje go, rozumie problem i stara się go uniknąć, po co taka składnia?
python
arguments
default-value
mutable
Jonathan
źródło
źródło
__init__
funkcji dla klasy, który jest ustawiany w zmiennej instancji; jest to całkowicie słuszna rzecz, którą chcieć zrobić, i wszystko idzie strasznie źle w przypadku zmiennego domyślnego. stackoverflow.com/questions/43768055/…Odpowiedzi:
Możesz go użyć do buforowania wartości między wywołaniami funkcji:
def get_from_cache(name, cache={}): if name in cache: return cache[name] cache[name] = result = expensive_calculation() return result
ale zwykle tego rodzaju rzeczy robi się lepiej z klasą, ponieważ możesz wtedy mieć dodatkowe atrybuty, aby wyczyścić pamięć podręczną itp.
źródło
@functools.lru_cache(maxsize=None)
lru_cache
jest niedostępne, jeśli masz wartości, których nie można mieszać.Kanoniczną odpowiedzią jest ta strona: http://effbot.org/zone/default-values.htm
Wymienia również 3 „dobre” przypadki użycia dla mutowalnego argumentu domyślnego:
źródło
Może nie zmieniasz mutowalnego argumentu, ale spodziewasz się mutowalnego argumentu:
def foo(x, y, config={}): my_config = {'debug': True, 'verbose': False} my_config.update(config) return bar(x, my_config) + baz(y, my_config)
(Tak, wiem, że możesz użyć
config=()
w tym konkretnym przypadku, ale uważam, że jest to mniej jasne i mniej ogólne).źródło
import random def ten_random_numbers(rng=random): return [rng.random() for i in xrange(10)]
Używa
random
modułu, efektywnie modyfikowalnego singletona, jako domyślnego generatora liczb losowych.źródło
random
raz na wywołanie funkcji”. Oba kończą się użyciem tego samego przedmiotu.EDYCJA (wyjaśnienie): Problem z modyfikowalnym domyślnym argumentem jest objawem głębszego wyboru projektu, a mianowicie tego, że domyślne wartości argumentów są przechowywane jako atrybuty obiektu funkcji. Możesz zapytać, dlaczego dokonano takiego wyboru; jak zawsze na takie pytania trudno odpowiedzieć właściwie. Ale z pewnością ma dobre zastosowania:
Optymalizacja pod kątem wydajności:
def foo(sin=math.sin): ...
Przechwytywanie wartości obiektów w zamknięciu zamiast zmiennej.
callbacks = [] for i in range(10): def callback(i=i): ... callbacks.append(callback)
źródło
Wiem, że to jest stary, ale tak na marginesie chciałbym dodać przypadek użycia do tego wątku. Regularnie piszę niestandardowe funkcje i warstwy dla TensorFlow / Keras, przesyłam moje skrypty na serwer, trenuję tam modele (z niestandardowymi obiektami), a następnie zapisuję modele i pobieram je. Aby załadować te modele, muszę następnie dostarczyć słownik zawierający wszystkie te niestandardowe obiekty.
To, co możesz zrobić w sytuacjach takich jak moja, to dodać kod do modułu zawierającego te niestandardowe obiekty:
custom_objects = {} def custom_object(obj, storage=custom_objects): storage[obj.__name__] = obj return obj
Następnie mogę po prostu ozdobić dowolną klasę / funkcję, która musi znajdować się w słowniku
@custom_object def some_function(x): return 3*x*x + 2*x - 2
Co więcej, powiedzmy, że chcę przechowywać moje niestandardowe funkcje utraty danych w innym słowniku niż moje niestandardowe warstwy Keras. Korzystanie z functools.partial daje mi łatwy dostęp do nowego dekoratora
import functools import tf custom_losses = {} custom_loss = functools.partial(custom_object, storage=custom_losses) @custom_loss def my_loss(y, y_pred): return tf.reduce_mean(tf.square(y - y_pred))
źródło
W odpowiedzi na pytanie o dobre zastosowania zmiennych domyślnych wartości argumentów, przedstawiam następujący przykład:
Zmienna wartość domyślna może być przydatna do programowania łatwych w użyciu, możliwych do zaimportowania poleceń własnego autorstwa. Zmienna domyślna metoda sprowadza się do posiadania prywatnych, statycznych zmiennych w funkcji, którą można zainicjować przy pierwszym wywołaniu (bardzo podobnie do klasy), ale bez konieczności uciekania się do zmiennych globalnych, bez konieczności używania opakowania i bez konieczności tworzenia instancji obiekt klasy, który został zaimportowany. Jest na swój sposób elegancki, na co mam nadzieję, że się zgodzisz.
Rozważ te dwa przykłady:
def dittle(cache = []): from time import sleep # Not needed except as an example. # dittle's internal cache list has this format: cache[string, counter] # Any argument passed to dittle() that violates this format is invalid. # (The string is pure storage, but the counter is used by dittle.) # -- Error Trap -- if type(cache) != list or cache !=[] and (len(cache) == 2 and type(cache[1]) != int): print(" User called dittle("+repr(cache)+").\n >> Warning: dittle() takes no arguments, so this call is ignored.\n") return # -- Initialize Function. (Executes on first call only.) -- if not cache: print("\n cache =",cache) print(" Initializing private mutable static cache. Runs only on First Call!") cache.append("Hello World!") cache.append(0) print(" cache =",cache,end="\n\n") # -- Normal Operation -- cache[1]+=1 # Static cycle count. outstr = " dittle() called "+str(cache[1])+" times." if cache[1] == 1:outstr=outstr.replace("s.",".") print(outstr) print(" Internal cache held string = '"+cache[0]+"'") print() if cache[1] == 3: print(" Let's rest for a moment.") sleep(2.0) # Since we imported it, we might as well use it. print(" Wheew! Ready to continue.\n") sleep(1.0) elif cache[1] == 4: cache[0] = "It's Good to be Alive!" # Let's change the private message. # =================== MAIN ====================== if __name__ == "__main__": for cnt in range(2):dittle() # Calls can be loop-driven, but they need not be. print(" Attempting to pass an list to dittle()") dittle([" BAD","Data"]) print(" Attempting to pass a non-list to dittle()") dittle("hi") print(" Calling dittle() normally..") dittle() print(" Attempting to set the private mutable value from the outside.") # Even an insider's attempt to feed a valid format will be accepted # for the one call only, and is then is discarded when it goes out # of scope. It fails to interrupt normal operation. dittle([" I am a Grieffer!\n (Notice this change will not stick!)",-7]) print(" Calling dittle() normally once again.") dittle() dittle()
Jeśli uruchomisz ten kod, zobaczysz, że funkcja dittle () internalizuje się przy pierwszym wywołaniu, ale nie przy dodatkowych wywołaniach, używa prywatnej statycznej pamięci podręcznej (zmienna wartość domyślna) do wewnętrznej pamięci statycznej między wywołaniami, odrzuca próby przejęcia pamięć statyczna jest odporna na złośliwe dane wejściowe i może działać w oparciu o warunki dynamiczne (w tym przypadku na temat liczby wywołań funkcji).
Klucz do używania mutable defaults nie robi niczego, co spowoduje ponowne przypisanie zmiennej w pamięci, ale zawsze zmienia zmienną w miejscu.
Aby naprawdę zobaczyć potencjalną moc i użyteczność tej techniki, zapisz ten pierwszy program w bieżącym katalogu pod nazwą „DITTLE.py”, a następnie uruchom następny program. Importuje i wykorzystuje nasze nowe polecenie dittle () bez konieczności zapamiętywania jakichkolwiek kroków ani programowania przeskakiwania tamborków.
Oto nasz drugi przykład. Skompiluj i uruchom to jako nowy program.
from DITTLE import dittle print("\n We have emulated a new python command with 'dittle()'.\n") # Nothing to declare, nothing to instantize, nothing to remember. dittle() dittle() dittle() dittle() dittle()
Czy to nie jest tak gładkie i czyste, jak to tylko możliwe? Te zmienne wartości domyślne mogą naprawdę się przydać.
========================
Po chwili zastanowienia się nad moją odpowiedzią nie jestem pewien, czy zrobiłem różnicę między używaniem domyślnej metody mutowalnej a zwykłym sposobem osiągania tego samego.
Zwykłym sposobem jest użycie funkcji możliwej do zaimportowania, która otacza obiekt klasy (i używa funkcji globalnej). Dla porównania, tutaj metoda oparta na klasach, która próbuje zrobić to samo, co domyślna metoda mutowalna.
from time import sleep class dittle_class(): def __init__(self): self.b = 0 self.a = " Hello World!" print("\n Initializing Class Object. Executes on First Call only.") print(" self.a = '"+str(self.a),"', self.b =",self.b,end="\n\n") def report(self): self.b = self.b + 1 if self.b == 1: print(" Dittle() called",self.b,"time.") else: print(" Dittle() called",self.b,"times.") if self.b == 5: self.a = " It's Great to be alive!" print(" Internal String =",self.a,end="\n\n") if self.b ==3: print(" Let's rest for a moment.") sleep(2.0) # Since we imported it, we might as well use it. print(" Wheew! Ready to continue.\n") sleep(1.0) cl= dittle_class() def dittle(): global cl if type(cl.a) != str and type(cl.b) != int: print(" Class exists but does not have valid format.") cl.report() # =================== MAIN ====================== if __name__ == "__main__": print(" We have emulated a python command with our own 'dittle()' command.\n") for cnt in range(2):dittle() # Call can be loop-driver, but they need not be. print(" Attempting to pass arguments to dittle()") try: # The user must catch the fatal error. The mutable default user did not. dittle(["BAD","Data"]) except: print(" This caused a fatal error that can't be caught in the function.\n") print(" Calling dittle() normally..") dittle() print(" Attempting to set the Class variable from the outside.") cl.a = " I'm a griefer. My damage sticks." cl.b = -7 dittle() dittle()
Zapisz ten program oparty na klasie w swoim bieżącym katalogu jako DITTLE.py, a następnie uruchom następujący kod (taki sam jak wcześniej).
from DITTLE import dittle # Nothing to declare, nothing to instantize, nothing to remember. dittle() dittle() dittle() dittle() dittle()
Porównując te dwie metody, korzyści wynikające z używania zmiennego ustawienia domyślnego w funkcji powinny być wyraźniejsze. Zmienna domyślna metoda nie wymaga zmiennych globalnych, jej zmiennych wewnętrznych nie można ustawić bezpośrednio. I chociaż metoda mutowalna zaakceptowała przekazany przez wiedzę argument dla pojedynczego cyklu, a następnie zlekceważyła go, metoda Class została trwale zmieniona, ponieważ jej zmienne wewnętrzne są bezpośrednio widoczne na zewnątrz. Którą metodę łatwiej zaprogramować? Myślę, że zależy to od Twojego komfortu z metodami i złożonością celów.
źródło