zwei Fragen zum Python Tutorial

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich lese gerade #ausgründen das Python-Tutorial https://docs.python.org/3/tutorial. Im Kapitel zu Klassen habe ich zwei Verständnisprobleme. Im Sinne von: was will der Autor sagen?

1. "A scope is a textual region of a Python program where a namespace is directly accessible. “Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace." -> was soll der Teil ab "... than an unqualified reference..." bedeuten? Also worauf bezieht sich das "unqualifiziert"?
2. Ein paar Abschnitte später kommt mehrfach "textual" bzw. "textually" vor - heißt übersetzt "textuell", macht nach meinem Verständnis aber keinen Sinn. Kann es sein, dass das hier eher "kontextbezogen" heißen soll?

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@noisefloor: Ad 1. nur der nackte Name ohne irgendwas davor mit dem Punktoperator. Also nicht `sys.argv` oder `self.attribute`.

Ad 2. Es geht da um die „textuell aktuelle Funktion“, also der Textbereich im Quelltext der den Funktionskörper ausmacht, nicht aktuelle Funktion im Sinne von welche Funktion zur Laufzeit aktuell ausgeführt wird/aktiv ist. In dem Absatz der mit „It is important to realize that scopes are determined textually:“ wird doch genau das erklärt was „textually“ hier bedeutet.

Edit: Traditioneller heisst das übrigens lexical scope.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

„Unqualified“ heißt, dass ein Name allein reicht, um das Objekt zu identifizieren, z. B. `a`. „Qualified“ bedeutet, dass das Objekt über Attributzugriff identifiziert wird, also z. B. `module.name` oder `self.attribute`. Das „qualifiziert“ bezieht sich darauf, dass der Name des Objektes durch einen anderen Namen „qualifiziert“ wird.

Beispiel:

Code: Alles auswählen

a = 42
print(a)  # beides unqualifiziert; a ist im innersten Scope (also lokal meist lokal); print ist im builtin-Scope

class C:
    def method(self):
        print("method")

    other_method = method  # unqualifiziert, weil der Körper der Klasse einen Scope bildet

    def f(self):
        method  # NameError: unqualifizierter Zugriff reicht nicht, weil method nicht in einem der vier Scopes enthalten ist, die durchsucht werden
        C.method  # C wird unqualifiziert benutzt (funktioniert, weil C im globalen Scope enthalten ist); method wird qualifiziert benutzt, weil es ein Attribut von C ist
„Kontextbezogen“ statt „textuell“ macht meinem Verständnis nach keinen Sinn. Da würde ich dann erwarten, dass das hier funktioniert und 42 ausgibt:

Code: Alles auswählen

def f():
    print(x)

def g():
    x = 42
    f()

g()
In `f` gibt es textuell kein `x` im lokalen oder einem der umschließenden Scopes. Kontextbezogen schon: `g` hat ein `x` im Scope, also könnte das in `f` gefunden werden, wenn der Kontext mit einbezogen würde. Es gibt Sprachen, in denen das genau so funktioniert (frühe LISPs, soweit ich weiß), und da wird das üblicherweise „dynamic scoping“ genannt, weil der dynamische Kontext bestimmt, ob ein Name in Scope ist oder nicht.

In Python kann man den Scope von Namen statisch (also ohne das Programm auszuführen) bestimmen, alleine indem man sich den Quelltext anguckt. Deswegen „textuell“.

Falls dich das Thema tiefer interessiert, empfehle ich Crafting Interpreters, ein Buch, das die Implementierung einer relativ Python-ähnlichen Sprache beschreibt. Zwei Abschnitte gehen im besonderen auf Scoping ein: Das Kapitel über Statements für den Scope innerhalb einer Funktion (in der dort implementierten Sprache komplizierter als in Python, weil Blöcke anders als in Python dort eigene Scopes aufmachen) und Kapitel 10 und 11 über die Implementierung von Scopes in Bezug auf verschachtelte Funktionsaufrufe und Closures.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ok, vielen Dank!

Was das praktisch bedeutet war mit schon klar, aber die Nomenklatur hate ich in der Form noch nicht gehört.

> Falls dich das Thema tiefer interessiert, ...
Nee, eigentlich nicht. Der Hintergrund ist ein anderer. Dazu gibt es in den kommenden Tagen / Wochen mehr Infos in einem neuen Thread.

Gruß, noisefloor
Benutzeravatar
snafu
User
Beiträge: 6743
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wobei in Python ja auch Klamotten wie so etwas möglich sind:

Code: Alles auswählen

def func():
    print(x)

func.__globals__["x"] = 42

if __name__ == "__main__":
    func()
Ist das dann auch noch als ein textueller Ansatz zu verstehen? Also klar, man sieht immer noch, was passiert. Aber ist es hier nicht schon eine Vermischung mit dem Kontext? Natürlich schreibt man üblicherweise sein Python-Programm nicht so, aber die Sprache erlaubt es nun mal.
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@snafu: Ja, ist immer noch rein textuell. Vergleiche mit:

Code: Alles auswählen

def f():
    locals()["x"] = 42
    print(x)
Es ist statisch festgelegt, in welchem Scope der Name gesucht wird. Dass z. B. der `globals`-Scope mutable ist (oder durch ein komplett anderes Dictionary ausgetauscht werden kann) ändert nichts daran, dass `print(x)` beide Namen im globalen Scope sucht.
mm96
User
Beiträge: 30
Registriert: Donnerstag 26. November 2020, 23:24

Jetzt bin ich etwas verwirrt:
In einem anderen Thread hieß es mal, Pyhton kenne keinen lexical scope:
viewtopic.php?t=57026
Das x in foo() und das x in undo_foo() sind nicht dasselbe x. Python kennt keinen Lexical Scope.
Dann steht in der Dokumentation im bereits von noisefloor erwähnten Artikel:
Although scopes are determined statically, they are used dynamically. At any time during execution, there are 3 or 4 nested scopes whose namespaces are directly accessible:
und ein paar Absätze weiter unten:
On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)
Ich hab da jetzt den Überblick verloren, wann es dynamisch und wann statisch agiert, vielleicht könntet da nochmal etwas nachfüttern, das wäre super :).
Sehe ich es richtig, dass es früher mal dynamisch implementiert war und jetzt zu statisch geändert wurde?
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@mm96: Ich bin mir nicht sicher, welche Definition von „lexical scope“ pillmuncher in dem verlinkten Beitrag benutzt hat, weil das, was da beschrieben ist, nicht wirklich zu „Python kennt keinen Lexical Scope“ passt. Ich könnte mir am ehesten vorstellen, dass das ein Vertipper war und eigentlich dynamic scope gemeint war (weil dein Beispiel im Prinzip das gleiche ist wie mein Beispiel mit `print(x)`).

Die Verschachtelung von Scopes wird in Python statisch (textuell, lexical) ermittelt. Wenn eine Funktion textuell in einem Modul steht, ist ihr umschließender Scope der Modulscope genau dieses Moduls und nicht der des Moduls, in dem sie aufgerufen wird. Genau so bei Closures.

Wo ein Name gesucht wird, ist auch statisch festgelegt, allerdings gibt es zwei verschiedene Regeln:
  1. In Funktionen: Wenn eine Zuweisung an den Namen im lokalen Scope existiert, wird der Name immer in genau diesem Scope gesucht (auch wenn dynamisch die Zuweisung noch gar nicht ausgeführt wurde), sonst im umschließenden (und es geht mit Regel 2 weiter). In deinem Beispiel war im lokalen Scope von `undo_foo` eine Zuweisung an `x` (weil `-=` eine Zuweisung ist), deswegen war `x` ein lokalen Name in der Funktion und nicht ein gecaptureter aus der umgebenden Funktion.
  2. Außerhalb von Funktionen (also auf Modulebene und im Körper von Klassen): Ein Name wird von innen nach außen in allen umschließenden Scopes gesucht und der erste gefundene Name wird genommen. Bei einer Zuweisung wird der Name in den innersten Scope eingefügt. Beispiel:

    Code: Alles auswählen

    a = 27
    print("global", a)
    
    class C:
        print("in C", a)
        a = 42
        print("in C", a)
    
        class Inner:
            print("in Inner", a)
            a = 99
            print("in Inner", a)
    
        print("in C", a)
    
    print("global", a)
    
    Ausgabe:

    Code: Alles auswählen

    global 27
    in C 27
    in C 42
    in Inner 27
    in Inner 99
    in C 42
    global 27
    
    Dabei ist zu beachten, dass der umschließende Scope von einem Klassenscope immer der Scope des enthaltenden Moduls ist.
Regel 2 ist das, was im Tutorial mit „the actual search for names is done dynamically“ gemeint ist.

Was auch immer wieder Verwirrung hervorruft, ist, dass es in Python (anders als in den meisten anderen Sprachen) keine expliziten Variablendeklarationen gibt, sondern dass eine Zuweisung immer auch eine Variable im innersten Scope anlegt und sich nie auf eine Variable in einem äußeren Scope bezieht (dafür braucht man `global` oder `nonlocal`). Das könnte pillmuncher auch gemeint haben.
Manul
User
Beiträge: 53
Registriert: Samstag 13. Februar 2021, 16:00

narpfel hat geschrieben: Donnerstag 4. Januar 2024, 16:05 Falls dich das Thema tiefer interessiert, empfehle ich Crafting Interpreters, ein Buch, das die Implementierung einer relativ Python-ähnlichen Sprache beschreibt.
Vielen Dank für den Tip, das ist wirklich sehr spannend zu lesen.
Antworten