Einen Namensraum "hoch"

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
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Hiho,

ich versuche immer noch die Meschanik von Python zu verstehen. Nun stehe ich vor folgendem Problem. Kann ich irgendwie einen Namensraum 'höher' gehen?
Also ich habe zwei Dateien a.py und b.py. In a importiere ich b
import b

So, nun kann ich ja auf alles in b mit b.function() zugreifen. Aber geht das auch irgendwie rückwärts, also in b eine Stufe wieder hoch, also auf ein Element in a zugreifen? Oder verstehe ich da was vollkommen falsch?
Benutzeravatar
strogon14
User
Beiträge: 58
Registriert: Sonntag 23. Februar 2003, 19:34
Wohnort: Köln
Kontaktdaten:

Nein, das geht nicht. Der Modulnamensraum von b in a ist innerhalb des Moduls b der globale Namensraum.

Du könntest natürlich auch in b a importieren, das ist aber sehr schlechter Stil und deutet auf eine schlechte Modul/Klassenmodellierung hin.

Meist ist man mit folgenden zwei Möglichkeiten besser bedient:

a) Man packt alle Varaiablen in a, die auch in b verfügbar sein sollen, in eine Klasse und übergibt diese den Funktionen in b, die diese benötigen. (Z.B. eine Klasse für globale Konfigurationsparameter)

b) man lagert die entsprechenden Objekte in ein zusätzliches Modul c aus, das dann von beiden Modulen importiert wird.
Beyond
User
Beiträge: 227
Registriert: Freitag 6. September 2002, 19:06
Kontaktdaten:

Man kann immer auf die Module (d.h. den Namespace) zugreifen.

Falls Du aber kreuz-und-quer importierst, bekommst Du Probleme --- endlos Rekusion, wenn man im Modul-Level selbst importiert.

Falls soetwas nötig ist hat man meist etwas falsch designed.


Worum geht es konkret?

cu beyond
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Ich bastele gerade etwas mit tkinter rum und habe mir eine Datei gebaut in der ich den ganzen Menu-Kram unterbringen will. Verarbeitende Funktionen will ich aber gerne in eine andere Datei auslagern. Nun muss ich halt da zwei Wege gehen. Einmal die Inputs von dem GUI an die Verarbeitenden Funktionen. Und von dort die Ergebnisse zwecks Ausgabe zurück an die ausgebenden Funktionen...
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi AllesMeins,

das macht mann in der Regel durch die Übergabe der Funktionen, die durch die GUI ausgelöst werden als Parameter oder durch Vererbung auf eigene Klassen, die die GUI aufbauen. In Deinem konkreten Fall würde ich ein eigenes Modul machen, das die Methoden enthält und an das Modul, das die GUI aufbaut z.B. in einem Dictionary übergibt, welches dann anhand der Keys im Dictionary die passenden Funktionen an die GUI-Elemente/Menus übergibt.


Gruß

Dookie
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Hmm... das ging mir etwas schnell. Also mal ein Beispiel, ich habe eine Datei a.py und eine Datei gui.py. So, nun importiere ich gui in a. Danach rufe ich gui.menu() auf. In gui.menu steht dann irgendwas mit button = Button(....., command=???)
Was muss denn dann bei command hin. Wenn ich da ne andere Funktion aufrufe um die Rückgabe zu managen scheitert es ja daran das dann eben die Rückgabe nicht von menu() kommt. Also irgendwie ist das alles für mich noch ein Buch mit mindestens siebzig Siegeln. Könnt ihr mir mal ein simples Beispiel geben wie das gemeint ist. Also einfach ein Button oder so, der irgendwas so zurückschickt, das ich den entsprechenden Wert von a.py aus verwenden kann?
Ausserde, noch die Frage wie ist das mit komplizierteren Konstrukten (zum Bleistift beim Button-Drücken die Eingabe in einem Textfeld zurückgeben. Innerhalb einer Datei hab ich das geschafft, aber wieder dieses zurückgreifen wird einfach nichts...

Grüsse
Benutzeravatar
strogon14
User
Beiträge: 58
Registriert: Sonntag 23. Februar 2003, 19:34
Wohnort: Köln
Kontaktdaten:

Also, hier mal ein abstraktes Beispiel, etwas angelehnt an den Mechanismus von GTK:

Code: Alles auswählen

---> gui.py <---

class Button:
    
    def __init__(self, label):
        self.label = label

    def connect(signal, func, *args)
        [...]

---> end <---


---> a.py <---
import gui

class Foo:
    
    def __init__(self, ...):
        [...]
        self.button = gui.Button('Click me!')
        self.button.connect('clicked', self.button_clicked, self.button)


    def button_clicked(self, button):
        print "Button %s clicked!" % button.label

---> end <---
Wie du siehst, wird in Klasse 'Foo' - durch die 'connect-Methode des Buttons aus gui.py - eine Funktion mit einem Signal verbunden ('connected' eben). D.h. im Klartext, wenn der Button gedrückt ('clicked') wird, führe die Funktion (bzw. Methode) 'button_clicked' aus. (Wie diese Signale ausgelöst werden, lassen wir jetzt mal außen vor.)

Beachte, dass hier ein Funktions-Objekt übergeben wird, und nicht etwa die Funktion aufgerufen wird (keine Klammern hinter dem Funktionsnamen!).

Weiterhin werden noch jedesmal beim Aufruf der Funktion bestimmte Argumente mitgegeben, die auch beim Aufruf von 'connect()' definiert werden. Wenn ich hier das Objekt mitgebe, das das Signal empfangen hat (d.h. hier der Button), kann ich in der Signal-Funktion bequem auf alle Eigenschaften und Methoden dieses Objekts zugreifen. Wenn das Objekt, in einem anderen Fall, z.B. ein Textfeld wäre, hätte das z.B. eine Methode 'get_text()', die mir den Inhalt des Textfelds liefert.

Alle Klarheiten beseitigt? ;-)

Chris
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Um ehrlich zu sein, ich verstehe gar nichts. Ich glaube ich habe noch nie vor einem Stück Code gesessen und so wneig verstanden, wie jetzt.
Das erste Problem dürften die Verwendung von Klassen sein, von denen ich mich bisher immer gedrückt habe (meine Erfahrung ist, man muss nur das Wort 'class' irgendwo in die Nähe von einem Script bringen und alles was vorher funktioniert und gegolten hat gilt auf einmal nicht mehr).
Und auch so isses im Moment bei meinen Basteleien. Ich habe einfach mal alles was ich bisher gebaut habe in eine Klasse gepackt und nun verfolgt mich ein fehler nach dem Anderen - Befehle gehen auf einmal nicht mehr, Variablen sind plötzlich nicht definiert, irgendwelche Funktionen verlangen mehr oder auch mal weniger Argumente als vorher usw. - (obwohl das alles vorher ohne weiteres funktioniert hat und von aussen auch keinerlei Variablen oder Informationen oder sowas hinzu kamen, ich hab die Klasse mit Bedacht eben mal so gross wie möglich gewählt, also um alles drumgelegt, das ich vorher geschrieben hatte)
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Hi AllesMeins,

hier mal ein Beispiel:

Modul für die GUI:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
    Modul:          tkbeispielGUI
    Description:    Beschreibung
    Version:        V0.1
    Copyright:      2003 by Fritz Cizmarov fritz@sol.at
    Created:        22. Nov. 2003
    Last modified:  22. Nov. 2003
    License:        free
    Requirements:   Python2.3
    Exports:        Classes and Functions to export
"""

from Tkinter import *

noop = lambda : None #dummyfunktion

class MyGUI(object):
    def __init__(self, handlers):
        self.handlers = handlers
        self._buildgui()

    def _buildgui(self):
        self.root = Tk()
        self.statusvar = StringVar(self.root)
        self.button1 = Button(self.root, text=" 1 ", command=self.handlers.get("handle_button1",noop))
        self.button1.pack()
        self.button2 = Button(self.root, text=" 2 ", command=self.handlers.get("handle_button2",noop))
        self.button2.pack()
        self.label = Label(self.root, width=20, textvariable=self.statusvar)
        self.label.pack()

    def mainloop(self):
        self.root.mainloop()
Hauptmodul:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
    Modul:          tkbeispiel
    Description:    Beschreibung
    Version:        V0.1
    Copyright:      2003 by Fritz Cizmarov fritz@sol.at
    Created:        22. Nov. 2003
    Last modified:  22. Nov. 2003
    License:        free
    Requirements:   Python2.3
    Exports:        Classes and Functions to export
"""

from tkbeispielGUI import *

def do_button1():
    myGUI.statusvar.set("Button 1 angeklickt")
    print "Button 1"

def do_button2():
    myGUI.statusvar.set("Button 2 angeklickt")
    print "Button 2"

handler_dict = {
    "handle_button1" : do_button1,
    "handle_button2" : do_button2
}

myGUI = MyGUI(handler_dict)
myGUI.mainloop()
Wie Du siehst, übergebe ich an die GUIKlasse ein Dictionary mit den Händlern für die Buttons, in denen die Funktionen stehen, die bei einem Klick auf den Button ausgeführt werden. In den Funktionen, es könnten auch Methoden einer Klasse sein, wird dann in der GUI das Attribut statusvar geändert, was auch gleich zur Anzeige im Labelwidget führt und ein Text in die Konsole geschrieben.
Ist für einen Button kein Eintrag im Dictionary vorhanden so wird eine Dummyfunktion "noop" eingetragen, die ich mittels lambda im GUIModul erstellt habe.


Gruß

Dookie
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Ok, ich gehe den Code jetzt mal durch und frag zu jeder Zeile die mir nicht volkommen klar ist :)

Code: Alles auswählen

class MyGUI(object):
    def __init__(self, handlers):
        self.handlers = handlers
        self._buildgui()
__init__ wird automatisch aufgerufen, gell? Trotzdem ist mir nicht ganz klar was diese zwei Befehle da bewirken sollen. self._buildgui() ruft die Funktion auf, aber was ist self.handlers = handlers???

Code: Alles auswählen

        self.button1 = Button(self.root, text=" 1 ", command=self.handlers.get("handle_button1",noop))
Auch auf die Gefahr hin, das das das selbe ist wie meine vorige Frage:
self.handlers.get("handle_button1",noop) Was genau macht das?

Code: Alles auswählen

def do_button1():
    myGUI.statusvar.set("Button 1 angeklickt")
Der nächste Problemfall. myGUI kann ich noch verfolgen. Aber danach wirds schwerig. statusvar.set: Weisst das der Variable statusvar einen Wert zu, oder was macht das? Und vor allem, wieso klappt das, diese Variable müsste doch hiermit: self.statusvar = StringVar(self.root) wieder überschrieben werden.

Code: Alles auswählen

handler_dict = {
    "handle_button1" : do_button1,
    "handle_button2" : do_button2
}
Das hier sind quasi die Anweisungen, was zu tun ist, wenn button 1 oder button 2 gedrückt wird, ja? Hmm, jetzt nachdem ich hier angekommen bion und bestoimmt fünf mal drüber nachgedacht habe, fange ich an zu verstehen.

Also, mal der Ablauf, soweit ich ihn verstanden habe. Das was im Hauptteil handler_dict heisst landet irgendwie in der Klasse als self.handlers. self.handlers.get("handle_button1",noop) liest aus diesem handlers die entsprechende anweisung (do_button1) aus. Ich verstehe aber trotzdem immer noch nicht wie es dann zum Aufruf der eigentlichen Funktion kommt. Wieso kann ich dann beim command nicht gleich do_button1 schreiben, wenn das doch an sich der selbe Wert ist. Und wie kommt diese anweisung dann aus der Klasse raus, es gibt ja kein return oder sowas und auch nichts, das im hauptteil die Anweisungen entgegennimmt. Ich denke im groben habe ich verstanden, wie es läuft, aber noch lange nicht warum es überhaupt läuft...
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Na im Prinzip hast du es ja schon verstanden.
Nun zu Deinen Fragen.

Code: Alles auswählen

class MyGUI(object):
    def __init__(self, handlers):
        self.handlers = handlers
        self._buildgui() 
__init__ wird immer aufgerufen, wenn eine neue Instanz (ein neues Objekt) der Klasse erzeugt wird, beim Instanziieren - im Hauptmodul myGUI = MyGUI(handler_dict) - können an die Klasse so auch Parameter/Objekte mitübergeben werden.
Im konkreten Fall wird ein Dictionary mit den Funktionen die bei einer Aktion in der GUI ausgeführt werden sollen mitübergeben und dieses Dictionary in der Instanz als Attribut handlers gespeichert. Dies geschieht bei self.handlers = handlers. Dabei müssen das Attribut und der Parameter aber nicht den gleichen namen haben. es könnte auch heissen self.buttonhandlers = handlers, wobei dann später beim Aufbau der gui auch self.handlers durch self.buttonhandlers ersetzt werden muss.

Code: Alles auswählen

        self.button1 = Button(self.root, text=" 1 ", command=self.handlers.get("handle_button1",noop)) 
hmm das ist eine Eigenheit von mir, ich hätte es auch als

Code: Alles auswählen

        self.button1 = Button(self.root, text=" 1 ", command=self.handlers["handle_button1"] 
schreiben können, dann wärs gleich klarer, daß hier ein Eintrag vom Dictionary mit den Buttonhandlern, als command an den Button gebunden wird.
get(key, default) ist eine Methode von Dictionary, so wird, falls zu einem Button, oder anderen Widget, kein Eintrag im Dictionary steht, einen Dummyhandler bzw einen Universalhandler für den Button eingetragen. So kann ich in der Entwicklungsphase schon Buttons und andere Widgets einbauen, für die ich im Hauptmodul noch keine Funktion bereitgestellt habe.

Code: Alles auswählen

def do_button1():
    myGUI.statusvar.set("Button 1 angeklickt")
statusvar ist ein Objekt vom Type StringVar aus Tkinter. Diese VariablenObjekte sind ein schönes Werkzeug für die Kommunikation der GUI mit dem eigentlichen Programm, da sie an ein oder mehrere Widgets, auch z.B. an ein Entrywidget gebunden werden können, und dann bei Eingabe eines neuen Wertes ins Widget, durch den User, auch ihren Wert ändern und wenn der Wert des Variablenobjekts durch das Programm, wie in meinem Beispiel, geändert wird, diese Änderung sofort im damit verbundenem Widget angezeigt wird. Hier zeigt sich auch die Mächtigkeit von OOP, Du brauchst gar nicht zu wissen, wie und wo der Inhalt des Variablenobjekts in der GUI dargestellt wird, das weiss das Variablenobjekt selber und erledigt automatisch, wenn mit der Methode set(neuerWert) dem Objekt ein neuer Wert zugewiesen wird, alles weitere.
self.statusvar = StringVar(self.root) wird nur beim Initialisieren der GUI ausgeführt und wiest statusvar ein Objekt vom Type StringVar aus Tkinter zu. Danach wird nur noch der Inhalt des Objekts mit der Methode set(neuerWert ) vom Programm aus ein neuer Inhalt gebeben.

Code: Alles auswählen

handler_dict = {
    "handle_button1" : do_button1,
    "handle_button2" : do_button2
} 
ist einfach nur ein Dictionary mit den Keys "handle_button1" und "handle_button2" und den zugehörigen daten "do_button1" und "do_button2". Da auch Funktionen Objekte sind, könne diese an Variablen zugewiesen oder in Listen oder Dictionarys gespeichert werden.
Das was im Hauptteil handler_dict heisst landet irgendwie in der Klasse als self.handlers. self.handlers.get("handle_button1",noop) liest aus diesem handlers die entsprechende anweisung (do_button1) aus. Ich verstehe aber trotzdem immer noch nicht wie es dann zum Aufruf der eigentlichen Funktion kommt. Wieso kann ich dann beim command nicht gleich do_button1 schreiben, wenn das doch an sich der selbe Wert ist.
Du kannst beim command eben nicht gleich do_button1 schreiben, weil diese Funktion weder in der Klasse noch im Modul sichtbar ist. Wenn Du nur ein Modul hättest ginge es auch so. Zum Aufruf der Funktion aus dem Hauptmodul kommt es, da hier mit Objekten, genauer mit Verweisen auf Objekte gearbeitet wird. bei __init__(self, handlers) wird nicht das Dictionary an MyGUI übergeben, sondern nur ein Verweis, der auf das Dictionary das im Hauptmodul definiert wurde. Genauso verhält es sich dann mit der Anweisung command = self.handlers.get("handle_button1", noop) hier wird ein Verweis aus dem Dictionary geholt und an den Button gebunden, falls kein passender Eintrag im Dictionary enthalten ist, wird ein Verweis auf noop an den Button gebunden.
Ich verwende hier den Ausdruck Verweis anstelle von Pointer, da Python, zum Glück, keine Pointerarithmetik kennt. Falls Du Erfahrung mit C++ oder Java hast, kannst du statt Verweis auch Pointer sagen, allerdings mit der eben genannten Einschränkung.


Gruß

Dookie
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Hmm... langsam nähere ich mich dem, was man verstehen nennen kann. Aber trotzdem kommt mir das doch alles ziemlich unhandlich vor. Die Komunikation über dieses dictionary kann ja nicht alles sein. Was ist zum Beispiel wenn ich Benutzereingaben aus der Klasse raus bringen will. Also ich habe ein Texteingabefeld und das was da drinne steht soll eine Funktion draussen verwenden können. Bei den Buttons ging das ja gut, wenn gedrückt mache dieses, das its ja schön statisch. Aber sowas wie wen ausgefüllt mach dieses mit dem ausgefüllten Wert, lässt sich über diesen Dictionary-Spass ja nicht mehr regeln...
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Das geht am komfortabelsten wieder über ein Variableobjekt, welches mit dem Entrywidget verbunden ist. An ein solches Variableobjekt kann auch eine Funktion gebunden werden, die bei Änderung des Inhalts ausgeführt wird.

Ändern wir mal das Beispiel so, daß für die Statusanzeige ein Entrywidget verwendet wird.

im GUIModul kommen folgende Zeilen dazu:
nach der Zeile self.statusvar = StringVar(self.root) fügen wir die Zeile:

Code: Alles auswählen

        self.statusvar.trace_variable("w", self.handlers.get("handle_status", noop))
ein. und die Zeilen

Code: Alles auswählen

        self.label = Label(self.root, width=20, textvariable=self.statusvar)
        self.label.pack()
ändern wir in

Code: Alles auswählen

        self.entry = Entry(self.root, width=20, textvariable=self.statusvar)
        self.entry.pack()
Im Hauptmodul fügen wir noch filgende Funktion hinzu:

Code: Alles auswählen

def do_status(*args):
    print "Neuer Status = %s" % myGUI.statusvar.get()
das neue Handerdict schaut nun so aus:

Code: Alles auswählen

handler_dict = {
    "handle_button1" : do_button1,
    "handle_button2" : do_button2,
    "handle_status"  : do_status
}
Bei der Änderung des Inhalts des Entrywidgets wird der neue Inhalt auf der Konsole ausgegeben. Dies passiert auch, wenn der Inhalt, durch ein Klick auf einen Button erfolgt, ohne daß wir hierfür extra etwas Programmieren mussten.


Gruß

Dookie
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Nagut, ich sehe ich hab noch viel zu lernen. Aber immerhin bin ich nun schon ein ganzes Stück weiter als noch heut morgen :) Aber ein paar weitere Fragen habe ich immer noch:

self.statusvar.trace_variable("w", self.handlers.get("handle_status", noop))

Das klappt ja an sich ganz gut. Nur das Problem ist, das handle_status jedes mal aufgerufen wird, wenn sich im Textfeld etwas ändert. Also, wenn jemand 12345 eingibt wird handle_status einmal mit 1, einmal mit 12, einmal mit 123 usw. aufgerufen. Das ist natürlich ziemliche Vergeudung. Also hab ich versucht den Spass direkt auf 'noop' umzuleiten. Da klappt aber auch nicht, aufgrund der misteriösen Argumente, die immer mitgegeben werden (TypeError: <lambda>() takes no arguments (3 given)). Was kann ich da machen? Und kann ich das ganze auch gleich ohne irgend eine Funktion verfügbar machen. Also der Wert des Textfeldes wird erst dann an die Funktion übergeben, wenn der Button "weiter" oder so gedrückt ist?

Und noch ne kleine Frage "w" steht für write?
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

ja das w steht für write. Das lambda kannst du noch ändern in noop = lambda *args: None #dummyfunktion dann klappts auch mit dem trace_variable. Man kann auch eine Callbackfunktion einbauen, die z.B. wenn das Entrywidget den Focus an ein anderes Widget verliert, aufgerufen wird und erst dann die geänderte Variable auswerten. Aber dazu würde ich pydoc oder die Tkinter-Dokumentation konsultieren. Natürlich kannst Du auch einen Button "weiter" erstellen und in dessen Funktion "statusvar" auswerten.

Dookie
AllesMeins
User
Beiträge: 63
Registriert: Donnerstag 20. November 2003, 13:45
Wohnort: Frankfurt/M.

Perfekt, jetzt funktioniert im Moment alles so wie ich es mir vorstelle. Ich bin mir zwar sicher, das ich in spätestens 3 bis 4 Stunden die nächste Frage habe aber bis dahin sei euch ne Pause gegönnt :)
Antworten