Closure Variablen bzw. Blockscoping

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.
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Hallo

ich versuche gerade tiefer in Python einzudringen und hätte da mal eine Frage:

Sprachen wie Lisp, JS und Perl kennen Closurevariablen, Lisp und Perl auch echtes Blockscoping (JS nur über Umwege mit with), gibt es dieses Programmiermuster auch in Python?

Grüße
Kurt

PS: mal ein primitives Beispiel in Perl zur Veranschauung, kann mir das bitte jmd in Python übersetzen, WP behaupten Closurevariablen gäbe es http://de.wikipedia.org/wiki/Closure andere Treffer behaupten aber generelles Blockscoping gäbe es nicht in Python.

Code: Alles auswählen

#!/usr/bin/perl

my $closure="leer";

{
	my $closure=0;

	sub set_closure {
		$closure=shift;
	}
	sub get_closure {
		return $closure;
	}
}


set_closure(10);		#
print get_closure();	#: 10
print $closure;		#: leer
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Geht solange du keine Namen neu bindest:

Code: Alles auswählen

>>> def make_storage():
...  ns = {}
...  def get(x):
...   return ns[x]
...  def set(x, y):
...   ns[x] = y
...  return get, set
... 
>>> get_storage, set_storage = make_storage()
>>> set_storage("foo", 42)
>>> get_storage("foo")
42
Die Closure Variablen stecken in einer Cell-Variable der Funktion:

Code: Alles auswählen

>>> get_storage.func_closure[0].cell_contents
{'foo': 42}
Was Blockscoping angeht: Das haben wir in Python auch nur bei Funktionen. Ist zwar nicht gerade hip aber schnell.
TUFKAB – the user formerly known as blackbird
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

mitsuhiko hat geschrieben:Geht solange du keine Namen neu bindest:
verstehe ich nicht ganz, was darf nicht neu definiert werden, die Funktion oder die Variable?

Dein Beispiel nutzt ein hash ns (ähm ...directory? edit: dictionary!) ginge das auch direkt mit der Variable?
mitsuhiko hat geschrieben: Was Blockscoping angeht: Das haben wir in Python auch nur bei Funktionen. Ist zwar nicht gerade hip aber schnell.
Gehen bei den Funktionen auch Mehrfachverschachtelungen?

In JS kann ich einen Workaround mit with bauen und sogar schachteln, gibts sowas in Python nicht ?

Code: Alles auswählen

javascript:
closure="leer";
with({closure:0}) {
  function set(arg) { closure=arg }
  function get() { return closure }
}

set(10);
alert(get());       //: 10     
alert(closure);   //: leer
Zuletzt geändert von Kurt Z am Sonntag 18. Mai 2008, 14:00, insgesamt 2-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Kurt Z hat geschrieben:
mitsuhiko hat geschrieben:Geht solange du keine Namen neu bindest:
verstehe ich nicht ganz, was darf nicht neu definiert werden, die Funktion oder die Variable?
Die Variable. Du kannst innerhalb der Closure nicht die Variablen des Kontextes neu binden, also wenn sie immutable sin, sind sie quasi read-only. Deswegen hat mitsuhiko ein Dict verwendet, das kann man innerhalb der Closure verändern.
Kurt Z hat geschrieben:Gehen bei den Funktionen auch Mehrfachverschachtelungen?
Ja, im großen und ganzen so tief du willst. Wird dann aber etwas chaotisch, wenn du mit dem ersten Zeichen schon über 80 Zeichen bist ;)
Kurt Z hat geschrieben:In JS kann ich einen Workaround mit with bauen und sogar schachteln, gibts sowas in Python nicht ?
Nein, Workarounds brauchst du nicht :)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Leonidas hat geschrieben:
Kurt Z hat geschrieben:verstehe ich nicht ganz, was darf nicht neu definiert werden, die Funktion oder die Variable?
Die Variable. Du kannst innerhalb der Closure nicht die Variablen des Kontextes neu binden, also wenn sie immutable sin, sind sie quasi read-only.
OK das heißt ich kann einfachen Variablen im Kontext des Closure keine neuen Werte zuweisen, weil das vermutlich intern in Python bedeuten würde eine neue gleichnamige anzulegen, was einfache Kontextvariablen effektiv readonly macht.

Korrekt?

Code: Alles auswählen

>>> def make_storage():
...  closure=0
...  def set(x):
...   closure=x
...  def get():
...   return closure
...  return get, set
...
>>>
>>> get_storage, set_storage = make_storage()
>>> set_storage(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: 'int' object is not callable
>>> get_storage()
0
EDIT: Code berichtigtl
Leonidas hat geschrieben:Nein, Workarounds brauchst du nicht :)
also ich brauche dieses Muster sehr oft, vermute aber das man in Python dann wohl eher auf OOP zurückgreift.

danke euch beiden! :)
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Kurt Z hat geschrieben:Also ich brauche dieses Muster sehr oft, vermute aber das man in Python dann wohl eher auf OOP zurückgreift.
Nicht wirklich. In Python verwendet man sehr häufig Closures. Immerhin ist der Support dafür sehr gut (es leakt nicht da es die Closure Variablen markiert und in Cells packt). Das man Namen nicht neu binden kann mag als Einschränkung erscheinen (und wird mit Python 3 auch durch das einführen von "nonlocal" wegfallen) ist aber in der wirklichen Welt kein Problem.

Erstens kannst du dicts zum Ablegen von Werten verwenden, zweitens muss man seltenst wirklich Ints/Strings/Booleans/Tuples in einen höheren Scope setzen.
TUFKAB – the user formerly known as blackbird
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

mitsuhiko hat geschrieben:
Kurt Z hat geschrieben:Also ich brauche dieses Muster sehr oft, vermute aber das man in Python dann wohl eher auf OOP zurückgreift.
Nicht wirklich. In Python verwendet man sehr häufig Closures.
Ich meinte Blockscopes.
mitsuhiko hat geschrieben: Das man Namen nicht neu binden kann mag als Einschränkung erscheinen (und wird mit Python 3 auch durch das einführen von "nonlocal" wegfallen) ist aber in der wirklichen Welt kein Problem.
Python 3000 ist sowas wie Perl 6 oder?

Was heißt hier neu binden ? Ints sind in Py keine Primitiva sondern eine Art Objekt ???
mitsuhiko hat geschrieben:Erstens kannst du dicts zum Ablegen von Werten verwenden, zweitens muss man seltenst wirklich Ints/Strings/Booleans/Tuples in einen höheren Scope setzen.
Naja wenn ich in Perl eine globale Variable benutze und sie nachträglich kapseln möchte mache ich nen Block drum ohne den Code umfaktorieren zu müssen ( Int -> Dict) . Wie gezeigt geht das auch in JS.

Leakingprobleme habe ich da noch nie gesehen! (d.h. wenn man die Garbage Collection nicht austrickst und den Referenzzähler irgendwie inkrementiert oder Ringstrukturen baut, aber für sowas muss man sich schon anstrengen.)
Zuletzt geändert von Kurt Z am Sonntag 18. Mai 2008, 14:42, insgesamt 1-mal geändert.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

mitsuhiko hat geschrieben:Das man Namen nicht neu binden kann mag als Einschränkung erscheinen (und wird mit Python 3 auch durch das einführen von "nonlocal" wegfallen) ist aber in der wirklichen Welt kein Problem.
Ja, ich habe auch über ``nonlocal`` nachgedacht, aber eigentlich scheint mir das kein in der Realität relevantes Problem zu sein. Wenn ich Werte habe und die durch Funktionen modifizieren will, dann nehme ich keine Closures sondern eine Klasse. Da habe ich dann auch gleich noch die Möglichkeit Properties zu verwenden.

Nein, Python 3000 ist nicht mit Perl 6 vergleichbar, denn Python 3.0 Alpha 5 ist bereits verfügbar und im September wird wohl 3.0 rauskommen.

Und ja, ``int`` ist in Python ein Objekt.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Leonidas hat geschrieben:Nein, Python 3000 ist nicht mit Perl 6 vergleichbar, .
In einigen Dingen wohl schon: ...is a new version of the language that is incompatible with the 2.x line of releases.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Kurt Z hat geschrieben:
Leonidas hat geschrieben:Nein, Python 3000 ist nicht mit Perl 6 vergleichbar, .
In einigen Dingen wohl schon: ...is a new version of the language that is incompatible with the 2.x line of releases.
Ja, aber es wird immer noch für Python-Entwickler die gleiche Sprache sein minus einiger hässlicher Sachen, die in Python 2.x zwecks Kompatibilität drin sind.
Ganz besonders unterscheidet es sich von Perl 6 darin, dass es keine Vaporware ist.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

bitte kein Flame [1]
Ich kenne Perls Schwächen... überzeuge mich von Pythons Stärken![2]

Was macht z.B. die Abwärtskompatibilität in Py3?

Können alte 2.x-Module weitergenutzt werden?

[1] NACHTRAG: ich mache hier keine Propaganda für eine andere Sprache, ich erkundige mich über die Python Features.

Welche Sprache drunterliegt ist mir Schnuppe solange ich meine Patterns ohne Verrenkungen abilden kann.

Das verschiedene Konzepte unterschiedliche Vor/Nachteile haben ist klar, man muss sie aber kennen um mit ihnen umgehen zu können.

[2] wäre ich sonst hier?
BlackJack

2.x Module können nicht in 3.0 benutzt werden, ausser sie beinhalten zufällig nichts was sich in 3.0 geändert hat. Das ist eher unwahrscheinlich.

Was eher wahrscheinlich ist, ist das man sauber programmierte alte Module, die sich an halbwegs aktuelle Idiome halten mit einem Skript (halb)automatisch auf 3.0 Syntax bringen kann.

Zum Verhältnis Perl 6 zu Python 3.0 würde ich Vaporware für Perl nicht als Flame bezeichnen. Ich habe jedenfalls den Eindruck es ist schon ewig angekündigt und es kommt einfach nicht aus den Startlöchern. Während Python 3.0 im selbsgesteckten Zeitplan zu liegen scheint. Ausserdem ist sind die Änderungen 2.x -> 3.0 nicht so gravierend, wie mir das bei Perl scheint. In Python 3.0 wird einiges altes weggeworfen und es gibt Detailverbesserungen.
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Kurt Z hat geschrieben:
mitsuhiko hat geschrieben:
Kurt Z hat geschrieben:Also ich brauche dieses Muster sehr oft, vermute aber das man in Python dann wohl eher auf OOP zurückgreift.
Nicht wirklich. In Python verwendet man sehr häufig Closures.
Ich meinte Blockscopes.
Eigene Scopes für if/for etc sind nur sinnvoll, wenn ich entweder sowas wie RAII hab oder so lange Funktionen, dass ich Namensprobleme bekomm. Python hat explicities Resourcen aufräumen entweder mit try/finally oder with also ist zumindest der RAII Punkt unnütz und bei langen Funktionen hab ich so und so ein Maintenance Problem. Ich geb zu, dass ich gerne ein anderes Scopingverhalten in Python habe, aber die Kosten währen so hoch, dass ich es dann doch nicht mehr will.
Python 3000 ist sowas wie Perl 6 oder?
Nein. Python 3 ist einfach nur ein Python ohne Altlasten und keine Vaporware.
Was heißt hier neu binden ? Ints sind in Py keine Primitiva sondern eine Art Objekt ???
Neu binden heißt "name = wert". Damit wird name neu gebunden. Und ja, ints sind Objekte vom typ int. In Python ist alles ein Objekt.
Naja wenn ich in Perl eine globale Variable benutze und sie nachträglich kapseln möchte mache ich nen Block drum ohne den Code umfaktorieren zu müssen ( Int -> Dict) . Wie gezeigt geht das auch in JS.
Da du in Python keine globalen Variablen hast sondern maximale welche, die im Modul rumkugeln ist das keine Situation die für Python in Frame kommt.
Leakingprobleme habe ich da noch nie gesehen! (d.h. wenn man die Garbage Collection nicht austrickst und den Referenzzähler irgendwie inkrementiert oder Ringstrukturen baut, aber für sowas muss man sich schon anstrengen.)
Keine Ahnung wie das in Perl läuft, aber JavaScript und Ruby halten eine Referenz auf Foo durch den Scope des Proc Objektes und es wird erst freigegeben, wenn der Proc strirbt (oder in JavaScript die Funktion):

Code: Alles auswählen

class Foo
  def foo
    return Proc.new { 42 }
  end
end

x = (1..10).map { Foo.new.foo }
Solange x nicht gelöscht wird hast du 10 Foo Objekte rumliegen obwohl der Proc nicht auf die Instanz zugreift.

Im übrigen darfst du nicht mit deiner Vorstellung von Perl auf Python zurennen weil das überaus schmerzhaft wird. Python hat ein anderes Konzept (gerade von Typen und Scoping) das man da mit Wissen aus anderen Sprachen nur begrenzt weiter kommt. Ich kann auch nicht einfach Ruby nehmen und erwarten, dass dort das Scoping von Python vorliegt.
TUFKAB – the user formerly known as blackbird
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Kurt Z hat geschrieben:Was macht z.B. die Abwärtskompatibilität in Py3?
Nichts. Es gibt sie per-se nicht. Python 3 ist da, um einige Altlasten loswegen zu können, die in Python 2.x sind. Wenn du deine Programme auf 3.x portieren willst, gibt es das Tool 2to3.
Kurt Z hat geschrieben:Welche Sprache drunterliegt ist mir Schnuppe solange ich meine Patterns ohne Verrenkungen abilden kann.

Das verschiedene Konzepte unterschiedliche Vor/Nachteile haben ist klar, man muss sie aber kennen um mit ihnen umgehen zu können.
Uh, Patterns 1:1 abzubilden ist eine schlechte Idee. Etwa das aus Java bekannte Visitor Pattern kann man in Python als Extrinsic Visitor implementieren aber auch (was ich sauberer finde) über Generische Funktionen (Multimethods). Wenn ich das Visitor-Pattern implementiert hätte dann hätte ich mir mehr Arbeit gemacht, als nötig.

Und wenn du meinst das meine Meinung zu Perl 6 ein Flamebait war - keine Sorge, war es nicht. Ich habe nur die aktuelle Situation beschrieben. So wie ich die Perl 6 VM, Parrot als ein interessantes Spielzeug ansehe, die man als Basis für neue Sprachen nehmen kann so ist bei Perl 6 zur Zeit nicht absehbar wann und ob das überhaupt fertig wird.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Was ich mich ja schon immer gefragt habe: Python kann freien Variablen in einer Funktion keinen neuen Wert zuweisen, aber dennoch gibt es den Bytecode STORE_DEREF, der doch genau dieses machen könnte. Oder verstehe ich da etwas falsch? Ich lese das so, dass hier einer Zelle (der Implementierung einer freien Variablen) ein neuer Wert gegeben wird. Zellen bilden die Closure. Ich fände nämlich - genau wie Kurt Z - es schon praktisch, wenn man den klassischen funktionalen Weg zu Objekten beschreiten könnte. Das gezeigte Beispiel ist ja der kanonische Beleg, dass OOP und FP gleichmächtig sind.

Stefan
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

Wieder viel gelernt! :) Zwar hab ich jetzt 10 neue Fragen aber, die stelle ich vielleicht lieber erst wenn ich wieder aus dem Urlaub zurück bin, meine Frau erwartet dass ich jetzt die Koffer packe.

VIELEN DANK
Kurt
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

sma hat geschrieben:Was ich mich ja schon immer gefragt habe: Python kann freien Variablen in einer Funktion keinen neuen Wert zuweisen, aber dennoch gibt es den Bytecode STORE_DEREF, der doch genau dieses machen könnte. Oder verstehe ich da etwas falsch?
Richtig. Den opcode gibts, aber die Syntax nicht. Wenn du brav bist und lieb nachfragst gibts vielleicht einen future import für nonlocal :-)
TUFKAB – the user formerly known as blackbird
audax
User
Beiträge: 830
Registriert: Mittwoch 19. Dezember 2007, 10:38

Ist doch schon angekündigt ;)
Kurt Z
User
Beiträge: 23
Registriert: Samstag 17. Mai 2008, 17:43

mitsuhiko hat geschrieben:
Was heißt hier neu binden ? Ints sind in Py keine Primitiva sondern eine Art Objekt ???
Neu binden heißt "name = wert". Damit wird name neu gebunden. Und ja, ints sind Objekte vom typ int. In Python ist alles ein Objekt.
so habe nun in ner Bahnhofsbuchhandlung kurz in einen O'Reilly reinschauen können und beginne das Konzept zu verstehen, der Punkt scheint zu sein das Python-Variablen per default lokal sind. Geschieht eine Zuweisung auf eine neue Variable, dann wird sie im aktuellen lokalen Namespace (Funktionsscope) angelegt, bzw. gebunden. Deswegen kann nicht schreibend auf eine Variable im closurekontext zugegriffen werden, weil eine neue lokale angelegt wird.

Korrekt?
mitsuhiko hat geschrieben: Im übrigen darfst du nicht mit deiner Vorstellung von Perl auf Python zurennen weil das überaus schmerzhaft wird.
Du ich kennen so manche Sprache, default local, sodass man nicht mehr schreibend auf nonlocal zugreifen kann, hab ich noch nie gesehen und ist IMHO der Erwähnung Wert.

Wir können gerne hier Javascript als Lingua Franca nehmen, Ruby kenne ich nur theoretisch (und im wesentlichen beschränkt auf sein Perl-Erbe.)

(BTW es kann nicht die eine Perl-Vorstellung geben, mit der man losrennen kann, weils eine Ansammlung unterschiedlicher Vorstellungen sind [z.B. zwei diametral unterschiedliche Variablentypen] Bei Python hingegen wird ja immer das Orthogonale, Homogene und Einfache gelobt, um die Reaktionen schonmal vorweg zu nehmen.)


Memoryleaks entstehen meines Wissens wenn die Garbage Collection eine Variable nicht entsorgen kann, weil die Variable nur anscheinend noch gebraucht wird. Z.B. bei Ringbezügen

Code: Alles auswählen

javascript:
var b=["B"];
var a=["A"];
a[1]=b;
b[1]=a;
alert(a[1][1][1]);


Die Arrays a und b sind gleichzeitig in Gebrauch und können deswegen nicht ohne weiteres von der GC abgeräumt werden => Memoryleak!

Wie Python das vermeiden soll, habe ich nicht verstanden. Variablen aus umhüllenden Kontexten sind zumindest in Perl nicht problematisch, weil eine Closurefunktion nur eine Referenz zum Namespace des Kontextes mitgeschleppt, wo bei Bedarf nachgeschaut wird und nicht eine komplette Kopie der Variablentabelle. Das JS es anders macht wäre mir auch komplett neu und unverständlich...
Zuletzt geändert von Kurt Z am Donnerstag 22. Mai 2008, 01:15, insgesamt 1-mal geändert.
BlackJack

CPython hat eine automatische Speicherbereinigung, die schaut ob Objekte noch durch eine aktive Referenz erreichbar sind. Wenn also ein Ring nicht mehr irgendwo, auch indirekt, an einen Namen gebunden ist, können alle Objekte im Ring entsorgt werden. So entsteht kein Speicherleck, aber die Freigabe geschieht bei Ringen verzögert.

Ist im Grunde wie bei Java und AFAIK auch bei .NET.
Antworten