Können Sie diese drei (täuschend) einfachen Probleme in Python lösen?

Von Beginn meines Weges als Softwareentwickler an habe ich es wirklich geliebt, mich mit den Innenseiten von Programmiersprachen auseinanderzusetzen. Ich war immer daran interessiert, wie diese oder jene Konstruktion funktioniert, wie dieses oder jenes Team funktioniert, was sich unter der Haube von syntaktischem Zucker befindet usw. Kürzlich stieß ich auf einen interessanten Artikel mit Beispielen dafür, wie veränderbare und unveränderliche Objekte in Python nicht immer offensichtlich funktionieren. Meiner Meinung nach ist der Schlüssel, wie sich das Verhalten des Codes in Abhängigkeit vom verwendeten Datentyp ändert, wobei die identische Semantik und die verwendeten Sprachkonstrukte beibehalten werden. Dies ist ein großartiges Beispiel für das Denken nicht nur beim Schreiben, sondern auch beim Verwenden. Ich lade alle ein, die Übersetzung zu lesen.







Versuchen Sie, diese drei Probleme zu lösen, und überprüfen Sie die Antworten am Ende des Artikels.



Tipp : Probleme haben etwas gemeinsam. Wenn Sie also zum zweiten oder dritten Problem übergehen, wird das erste Problem für Sie einfacher.



Erste Aufgabe



Es gibt mehrere Variablen:



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)


Was wird beim Drucken angezeigt lund s?



Zweite Aufgabe



Definieren wir eine einfache Funktion:



def f(x, s=set()):
    s.add(x)
    print(s)


Was passiert, wenn Sie anrufen:



>>f(7)
>>f(6, {4, 5})
>>f(2)


Dritte Aufgabe



Wir definieren zwei einfache Funktionen:



def f():
    l = [1]
    def inner(x):
        l.append(x)
        return l
    return inner

def g():
    y = 1
    def inner(x):
        y += x
        return y
    return inner


Was bekommen wir nach der Ausführung dieser Befehle?



>>f_inner = f()
>>print(f_inner(2))

>>g_inner = g()
>>print(g_inner(2))


Wie sicher sind Sie in Ihren Antworten? Lassen Sie uns Ihren Fall überprüfen.



Lösung des ersten Problems



>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


Warum reagiert die zweite Liste auf eine Änderung an ihrem ersten Element a.append(5), während die erste Liste dieselbe Änderung vollständig ignoriert x+=5?



Lösung des zweiten Problems



Mal sehen was passiert:



>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


Warten Sie, sollte nicht das letzte Ergebnis sein {2}?



Lösung des dritten Problems



Das Ergebnis wird folgendermaßen aussehen:



>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


Warum g_inner(2)hat sie nicht 3? Warum erinnert f()sich die innere Funktion an den äußeren Bereich, die innere Funktion g()jedoch nicht? Sie sind fast identisch!



Erläuterung



Was ist, wenn ich Ihnen sage, dass all diese Beispiele für seltsames Verhalten mit dem Unterschied zwischen veränderlichen und unveränderlichen Objekten in Python zusammenhängen?



Veränderbare Objekte wie Listen, Mengen oder Wörterbücher können an Ort und Stelle geändert werden. Unveränderliche Objekte wie numerische Werte und Zeichenfolgenwerte sowie Tupel können nicht geändert werden. Ihre "Veränderung" führt zur Schaffung neuer Objekte.



Erklärung der ersten Aufgabe



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


Da es xunveränderlich ist, x+=5ändert die Operation das ursprüngliche Objekt nicht, sondern erstellt ein neues. Das erste Element der Liste bezieht sich jedoch weiterhin auf das ursprüngliche Objekt, sodass sich sein Wert nicht ändert.



weil Bei einem veränderlichen Objekt a.append(5)ändert der Befehl das ursprüngliche Objekt (anstatt ein neues zu erstellen), und die Liste s"sieht" die Änderungen.



Erklärung der zweiten Aufgabe



def f(x, s=set()):
    s.add(x)
    print(s)

>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


Mit den ersten beiden Ergebnissen ist alles klar: Der erste Wert wird 7zu dem anfänglich leeren Satz hinzugefügt und es stellt sich heraus {7}; dann wird der Wert 6zum Satz addiert {4, 5}und erhalten {4, 5, 6}.



Und dann beginnen seltsame Dinge. Der Wert wird 2nicht zur leeren Menge hinzugefügt, sondern zu {7}. Warum? Der Anfangswert des optionalen Parameters wird snur einmal ausgewertet: Beim ersten Aufruf wird s als leerer Satz initialisiert. Und da es änderbar ist, wird es nach dem Anruf f(7)„an Ort und Stelle“ geändert. Der zweite Aufruf f(6, {4, 5})wirkt sich nicht auf den Standardparameter aus: Er wird durch eine Menge ersetzt {4, 5}, dh es handelt sich um {4, 5}eine andere Variable. Der dritte Aufruf f(2)verwendet dieselbe VariablesDies wurde beim ersten Aufruf verwendet, wird jedoch nicht als leerer Satz neu initialisiert, sondern von seinem vorherigen Wert übernommen {7}.



Daher sollten Sie keine veränderlichen Argumente als Standardargumente verwenden. In diesem Fall muss die Funktion geändert werden:



def f(x, s=None):
    if s is None:
        s = set()
    s.add(x)
    print(s)


Erklärung der dritten Aufgabe



def f():
   l = [1]
   def inner(x):
       l.append(x)
       return l
   return inner

def g():
   y = 1
   def inner(x):
       y += x
       return y
   return inner

>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


Hier geht es um Verschlüsse: Interne Funktionen erinnern sich daran, wie ihre externen Namespaces zum Zeitpunkt ihrer Definition ausgesehen haben. Oder zumindest sollten sie sich erinnern, aber die zweite Funktion macht die Poker-Oberfläche und verhält sich so, als hätte sie nichts von ihrem externen Namespace gehört.



Warum passiert dies? Bei der Ausführung l.append(x)ändert sich das veränderbare Objekt, das bei der Definition der Funktion erstellt wurde. Die Variable lbezieht sich jedoch weiterhin auf die alte Speicheradresse. Der Versuch, eine unveränderliche Variable in der zweiten Funktion zu ändern, führt jedoch dazu, dass y += xy auf eine andere Speicheradresse verweist: Das ursprüngliche y wird vergessen, was zu einem UnboundLocalError führt.



Fazit



Der Unterschied zwischen veränderlichen und unveränderlichen Objekten in Python ist sehr wichtig. Vermeiden Sie das in diesem Artikel beschriebene seltsame Verhalten. Besonders:



  • .
  • - .



All Articles