2017-12-11 3 views
2

Ich bin ein erfahrener Programmierer, aber neu in Python, so dass ich nicht weiß, ob das, was ich hier sehe, Verhalten ist spezifisch für Python, dass ich nicht bewusst bin. Ich programmiere normalerweise in C++ und C#, also habe ich Codebeispiele in beiden Sprachen eingefügt, um die unterschiedlichen Ergebnisse zu demonstrieren.Rekursionsverhalten in Python vs C# - Seltsame Ergebnisse

Ich mache eine rekursive Suchfunktion, die eine Zustandsstruktur verwendet, um Ergebnisse aufzuzeichnen. In C# verhält es sich wie erwartet, aber in Python scheint es (soweit ich das beurteilen kann) nicht zu unterscheiden zwischen dem Zustand, der im aktuellen/letzten rekursiven Aufruf existiert. Mir ist bewusst, dass Python Objekte per Referenz verwaltet (wie in C#) - ich mache eine Tiefenaufnahme des Objekts, bevor ich es weiter in die Suchfunktion übergebe.

Zuerst auf die C# -Version und Ergebnisse:

class SimState 
{ 
    public List<string> stateDescriptions = new List<string>(); 
    public SimState Clone() 
    { 
     return new SimState() { 
      stateDescriptions = new List<string>(this.stateDescriptions) 
     };    
    } 
} 
class Program 
{ 
    public void search_recursive(SimState state,int move,int recurseDepth) 
    { 
     if (recurseDepth > 2) 
      return; 
     state.stateDescriptions.Add(move.ToString()); 
     Console.WriteLine(String.Format("{0}: {1}", 
      recurseDepth, 
      String.Join(",", state.stateDescriptions))); 
     for(int n = 0;n < 2;n++) 
     { 
      var newState = state.Clone(); 
      this.search_recursive(newState, n + 1, recurseDepth + 1); 
     } 
    } 
    static void Main(string[] args) 
    { 
     var initialState = new SimState(); 
     new Program().search_recursive(initialState, 0, 0); 
     Console.ReadKey(); 
    } 
} 

Und die Ergebnisse: Das ist, was ich erwarten würde passieren. Es sucht alle Kombinationen bis zu einem Maximum. Tiefe 2:

0: 0 
1: 0,1 
2: 0,1,1 
2: 0,1,2 
1: 0,2 
2: 0,2,1 
2: 0,2,2 

So weit so gut. Nun, was ich dachte, war ein äquivalentes Python-Programm:

import copy 

class SimState: 
    stateDescriptions = [] 

def search_recursive(state: SimState, move, recurseDepth): 
    if(recurseDepth > 2): 
     return 
    state.stateDescriptions.append('{0}'.format(move)) 
    print('{0}: '.format(recurseDepth) + ','.join(state.stateDescriptions)) 
    for n in range(2): 
     newState = copy.deepcopy(state) 
     search_recursive(newState,n + 1,recurseDepth + 1) 

initialState = SimState() 
search_recursive(initialState,0,0) 

Und die Ergebnisse:

0: 0 
1: 0,1 
2: 0,1,1 
2: 0,1,1,2 
1: 0,1,1,2,2 
2: 0,1,1,2,2,1 
2: 0,1,1,2,2,1,2 

Es scheint, als ob Python nicht der Tatsache bewusst ist, dass ich in einem völlig neuen Zustand bin vorbei (über den neuen, tief kopierten 'newState'), und aktualisiert stattdessen den vorhandenen 'Zustand' von dem vorherigen rekursiven Aufruf. Ich kenne den Post auf SO ""Least Astonishment" and the Mutable Default Argument"), aber das scheint sich speziell auf Standardargumente zu beziehen, die hier nicht verwendet werden.

Ich verwende Python 3.6 in Visual Studio 2017.

ich etwas offensichtlich fehle? Danke

+0

Sie haben mich gerade in die richtige Richtung gezeigt. Ich dachte, meine Definition von stateDescriptions [] in der SimState-Klasse entsprach der Deklaration einer Member-Variable, aber wie Sie bereits erwähnt haben, handelt es sich um ein statisches Klassenattribut. Ich ersetzte es durch eine __init__ Methode und das Zuweisen von self.stateDescriptions = [] und es funktioniert jetzt wie erwartet. Danke für die schnelle Antwort. Warum habe ich eine Klasse benutzt? Das war nur ein reduziertes Beispiel basierend auf dem eigentlichen Problem, das ich zu lösen versuche, wo der SimState weitaus komplexer ist. –

+0

Macht Sinn. Wie auch immer, ich ging voran und fügte nur eine Antwort hinzu. Beachten Sie, dass Python eine ziemlich dynamische Sprache ist und dass das Objektmodell ein wenig anders ist als Ihre typische statisch typisierte OOP-Sprache. Zum Beispiel, selbst.stateDescriptions "prüft zuerst den Instanz-Namespace und wenn er ihn nicht findet, prüft er den Klassen-Namespace und alle Namespaces im MRO. Sie können jedoch * Klassenvariablen mit Instanzvariablen * überschreiben. Also, sagen Sie, Sie hatten eine Mischung aus * beiden * Ihrer ursprünglichen Definition und der modifizierten, dann erstellen Sie im Wesentlichen eine nutzlose Klassenvariable, die immer beschattet wird –

Antwort

3

Das Problem ist Ihre Klassendefinition. Was Sie in Python geschrieben haben, erzeugt eine Klassenvariable, die vielleicht von einem C# -Hintergrund kommt, man würde sagen, dass sie ein "statisches Attribut" erzeugt. In jedem Fall benötigen Sie eine Instanz Attribut, wie folgt aus:

In [2]: import copy 
    ...: 
    ...: class SimState: 
    ...:  def __init__(self): 
    ...:   self.stateDescriptions = [] 
    ...: 
    ...: def search_recursive(state: SimState, move, recurseDepth): 
    ...:  if(recurseDepth > 2): 
    ...:   return 
    ...:  state.stateDescriptions.append('{0}'.format(move)) 
    ...:  print('{0}: '.format(recurseDepth) + ','.join(state.stateDescriptions)) 
    ...:  for n in range(2): 
    ...:   newState = copy.deepcopy(state) 
    ...:   search_recursive(newState,n + 1,recurseDepth + 1) 
    ...: 
    ...: initialState = SimState() 
    ...: search_recursive(initialState,0,0) 
    ...: 
0: 0 
1: 0,1 
2: 0,1,1 
2: 0,1,2 
1: 0,2 
2: 0,2,1 
2: 0,2,2 

Auch ich bin mir nicht sicher, was Sie unter „Python-Objekte durch Verweis verwaltet“, aber Python unterstützt nicht-Anruf durch -Referenz Semantik. Seine Semantik ist ähnlich zu Java - außer dass Python eine reine OOP-Sprache ist, es gibt keine primitiven Typen und alles ist ein Objekt (also wäre es wie Java, außer alles ist ein Referenztyp). Diese Strategie heißt technisch call by object sharing

+0

Danke - ich dachte, etwas zu deklarieren, wie ich in einer Klasse hatte, würde ein Mitglied erstellen Variable, die offensichtlich nicht korrekt ist. Die Aktualisierung auf __init__ und die Zuweisung an diesem Punkt haben das Problem gelöst. –