2011-08-24 28 views
5

Załóżmy, że masz funkcję, która musi utrzymywać pewien stan i zachowywać się inaczej w zależności od tego stanu. Zdaję sobie sprawę z dwóch sposobów realizacji tego, gdzie państwo jest przechowywana w całości przez funkcję:Python - Atrybuty funkcji lub zmienne wartości domyślne

  1. Korzystanie atrybut funkcyjny
  2. Stosując zmienny wartość domyślną

pomocą lekko zmodyfikowaną wersję Felix Klings answer to another question , tutaj jest przykładowa funkcja, która może być użyta w re.sub(), więc tylko trzeci pasuje do regex zostanie zastąpiony:

Atrybut funkcji:

def replace(match): 
    replace.c = getattr(replace, "c", 0) + 1 
    return repl if replace.c == 3 else match.group(0) 

Zmienna wartość domyślna:

def replace(match, c=[0]): 
    c[0] += 1 
    return repl if c[0] == 3 else match.group(0) 

Dla mnie pierwszy wydaje czystsze, ale widziałem drugi bardziej powszechnie. Co jest lepsze i dlaczego?

+4

Ludzie po prostu nie myśleć o funkcji atrybutów, ponieważ nie są one, że powszechnie stosowane przez początkujących, ale _everyone_ myśli stosując zmienny wartość domyślną, ponieważ początkujący _wszystkie_ zostaje ugryziony przez nich co najmniej raz. – agf

Odpowiedz

4

Używam zamiast tego zamknięcia, bez efektów ubocznych.

Oto przykład (I właśnie zmodyfikował oryginalny przykład Felix Klings answer):

def replaceNthWith(n, replacement): 
    c = [0] 
    def replace(match): 
     c[0] += 1 
     return replacement if c[0] == n else match.group(0) 
    return replace 

A Zastosowanie:

# reset state (in our case count, c=0) for each string manipulation 
re.sub(pattern, replaceNthWith(n, replacement), str1) 
re.sub(pattern, replaceNthWith(n, replacement), str2) 
#or persist state between calls 
replace = replaceNthWith(n, replacement) 
re.sub(pattern, replace, str1) 
re.sub(pattern, replace, str2) 

Dla zmienny, co powinno się zdarzyć, jeśli ktoś zadzwonić zastąpić (mecz , c = [])?

Dla atrybutu złamałeś enkapsulacji (tak wiem, że pyton nie realizowane w klasach z powodów diff ...)

+0

+1 Zgoda - nie zdawałem sobie sprawy, że kontekst pytania * original * jest funkcją zagnieżdżoną. – Seth

+0

@ JasonR.Coombs Dodałem przykład –

+0

ah! mylące 'c = [0]' dla 'c [0]' ... – n611x007

1

Jak o:

  1. użyć klasy
  2. użyć zmiennej globalnej

To prawda, że ​​nie są one przechowywane w całości w obrębie funkcji. I prawdopodobnie użyć klasy:

class Replacer(object): 
    c = 0 
    @staticmethod # if you like 
    def replace(match): 
     replace.c += 1 
     ... 

Aby odpowiedzieć na to pytanie rzeczywistą, należy getattr. Jest to bardzo przejrzysty i czytelny sposób przechowywania danych na później. To powinno być dość oczywiste dla kogoś, kto czyta to, co próbujesz zrobić.

Zmienna domyślna wersja argumentu jest przykładem typowego błędu programowania (zakładając, że za każdym razem otrzymasz nową listę). Z tego tylko powodu uniknęłbym tego. Ktoś, kto to przeczyta później, może zdecydować, że to dobry pomysł, nie rozumiejąc w pełni konsekwencji. I nawet w tym przypadku wydaje się, że twoja funkcja działałaby tylko raz (twoja wartość c nigdy nie zostanie zresetowana do zera).

1

Dla mnie oba te podejścia wyglądać podejrzanie. Problemem jest wołanie o instancję klasy. Zwykle nie myślimy o funkcjach, jak utrzymywanie stanu między połączeniami; to jest klasa dla.

Powiedziałem, że wcześniej użyłem atrybutów funkcji dla tego rodzaju rzeczy. W szczególności, jeśli jest to funkcja jednorazowa zdefiniowana w innym kodzie (tj. Nie jest możliwa do użycia z dowolnego innego miejsca), po prostu przypisywanie jej atrybutom jest bardziej zwięzłe, niż definiowanie zupełnie nowej klasy i tworzenie jej wystąpienia.

Nigdy nie nadużywałbym wartości domyślnych. Istnieje duża przeszkoda do zrozumienia, ponieważ naturalnym celem wartości domyślnych jest dostarczanie domyślnych wartości argumentów, a nie utrzymywanie stanu między wywołaniami. Plus domyślny argument zachęca do podania wartości innej niż domyślna, a zwykle zachowujesz się bardzo dziwnie, jeśli robisz to za pomocą funkcji nadużywającej wartości domyślnych w celu utrzymania stanu.

2

Obydwa sposoby są dla mnie dziwne. Pierwsza jest jednak znacznie lepsza. Ale kiedy myślisz o tym w ten sposób: "Coś ze stanem, który może wykonywać operacje z tym stanem i dodatkowym wkładem", to naprawdę brzmi jak normalny obiekt. A gdy coś brzmi jak przedmiot, powinna ona być przedmiotem ... Więc moje rozwiązaniem byłoby użyć prostego przedmiotu z __call__ metody:

class StatefulReplace(object): 
    def __init__(self, initial_c=0): 
     self.c = initial_c 
    def __call__(self, match): 
     self.c += 1 
     return repl if self.c == 3 else match.group(0) 

a następnie można napisać w globalnej przestrzeni lub Twój moduł startowych:

replace = StatefulReplace(0) 
Powiązane problemy