Variable aus Funktion übergeben

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
DB7WN
User
Beiträge: 49
Registriert: Samstag 18. März 2017, 22:11

Hallo allerseits!
Ich möchte mich im Hobbyniveau etwas mit Python(einschll. TKinter) befassen. Leicht fortgeschrittene Kenntnisse habe ich erworben vor Olims Zeiten mit dBaseIII+ und später mit XPROFAN.
Als Übungsstück will ich ein Fenster mit drei Buttons bauen. Mit zwei Buttons sollen jeweils ein Verzeichnis als Quelle und Ziel ausgewählt werden, und mit dem dritten Button soll eine Kopie von Quellverz. zu Zielverz. gestartet werden - quasi das Erstellen eines Backups.
Nun komme ich schon bei den Grundlagen ins Stocken. Ich kapiere nicht, wie ich die Variable, die das gewählte Verzeichnis beinhaltet. an das Hauptprogramm übergebe. Verschiedenes habe ich schon ausprobiert, das folgende scheint mir am nächsten zu liegen. Allerdings bringt die Zeile 'global a' den Fehler: global name 'a' is not defined. Irgendwas Grundlegendes fehlt mir hier.

Code: Alles auswählen

def quellpfad():
   global a
    a=askdirectory()
    return a

knopf1 = Button(root, text='Quellverzeichnis', width=25, command=quellpfad)
knopf1.place(x=10, y=20)
qlabel = Label(root, text="                                                               ")
qlabel.place(x=300, y=20)
quelle = a
qlabel = Label(root, text=quelle)
qlabel.place(x=300, y=20)   
Zuletzt geändert von Anonymous am Sonntag 19. März 2017, 00:09, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@DB7WN: Vergiss sofort das es ``global`` gibt. Das ist keine Lösung das ist ein Problem. Für GUI-Programmierung braucht man objektorientierte Programmierung (OOP) als Grundlage.

`place()` solltest Du Dir auch gar nicht erst angewöhnen.

Und dann funktionieren GUIs nicht so wie Du das da versuchst. Man setzt erst die GUI auf und registriert für die verschiedenen Ereignisse auf die man reagieren möchte Rückrufe auf eigene Methoden und dann übergibt man die Kontrolle an die GUI-Hauptschleife. Nicht Du bestimmst linear den Programm Ablauf wie man das bei einem Konsolenprogramm mit `raw_input()` und ``print`` machen würde, sondern der Anwender bestimmt was er in welcher Reihenfolge anklickt/auslöst, und Deine Programmlogik muss daruf reagieren. Zu dem Zeitpunkt wo Du ``quelle = a`` ausführst, hat der Anwender ja noch gar nicht die Schaltfläche gedrückt, deshalb ist `a` nicht definiert. Er kann auch erst auf die Schaltfläche drücken wenn Du die Hauptschleife von Tk aufgerufen hast, vorher verarbeitet die GUI keine Benutzereingaben.
DB7WN
User
Beiträge: 49
Registriert: Samstag 18. März 2017, 22:11

Hallo BlackJack, danke für deine Antwort.
Im Prinzip ist mir schon klar, dass das GUI-Programmieren etwas anders zu gestalten ist, als das "geradeaus-programmieren" was ich sonst gewohnt war. Manchmal vergesse ich das aber. :?
OOP möchte ich für den Anfang mal vermeiden, weil das eine zusätzliche Schwierigkeit für mich ist, und so kleine, einfache Sachen müssten doch auch ohne OOP möglich sein.
Also ich habe das jetzt zum Laufen gebracht:
"global" brauche ich aber dafür. Wie soll ich sonst die Variablenwerte auf den Funktionen in die mainloop übertragen? Und warum eigentlich sollte man "global" vermeiden?

Code: Alles auswählen

from Tkinter import *
import tkFileDialog
import os
a=""
b=""
quelle = ""
ziel = ""

root = Tk()
root.geometry("600x200")
root.title("Pfadauswahl")

def quellpfad():
    global quelle
    quelle=tkFileDialog.askdirectory()
    qlabel = Label(root, text="                                                               ")
    qlabel.place(x=300, y=20)
    qlabel = Label(root, text=quelle)
    qlabel.place(x=300, y=20)   
                          
def zielpfad():
     global ziel
     ziel = tkFileDialog.askdirectory()
     zlabel = Label(root, text="                                                                ")
     zlabel.place(x=300, y=90)
     zlabel = Label(root, text=ziel)
     zlabel.place(x=300, y=90)

def start():
    startinfo = Label(root, text = quelle + "   >  " + ziel)
    startinfo.place(x=300, y=160)
    # kopieren starten

knopf1 = Button(root, text='Quellverzeichnis', width=25, command=quellpfad)
knopf1.place(x=10, y=20)
    
knopf2 = Button(root, text='Zielverzeichnis', width=25, command=zielpfad)
knopf2.place(x=10, y=90)

knopf3 = Button(root, bg="red", text = "Datensicherung starten", width=25, command=start)
knopf3.place(x=10, y=160)

root.mainloop()
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@DB7WN: oberstes Ziel eines jeden Programmierers sollte es sein, verständlichen Code zu schreiben. Nur wenn man ihn versteht, ist es möglich, zu prüfen, ob keine Fehler enthalten sind und das Programm auch das macht, was man will. ›global‹ mach Code unverständlich, weil man erst das ganze Programm durchsuchen muß, um zu verstehen, wo der Wert herkommt, wo er sich ändert, usw. Ebenso sollte man keine *-Importe verwenden, weil dadurch Variabeln-/Funktionsnamen auftauchen, deren Herkunft man nur schlecht nachvollziehen kann. Während Zweiteres relativ einfach durch "import Tkinter as tk" und der Referenzierung über z.B. "tk.Label" umzustellen ist, muß man um »gobal« zu vermeiden, teilweise die Struktur des Programms umstellen und bei GUIs geht das praktikabel nur über Klassen.

Fenster sind keine Leinwand, wo man Schicht um Schicht etwas übereinander malen sollte. Alle Elemente werden beim Erstellen richtig angeordnet (z.B. in einem Grid) und später werden nur noch Werte (z.B. eines Labels) geändert.

So könnte das ganze ohne »global« aussehen. Hat zusätzlich den Vorteil, dass man die beiden gleichen Funktionen quelle und ziel zu einer zusammenfassen kann. Hier wurde der Trick angewendet, dass Tk selbst objektorientiert arbeitet und man über StringVar-Objekte die Anzeige von Labels ändern kann:

Code: Alles auswählen

import Tkinter as tk
from functools import partial
import tkFileDialog
import os

def ask_directory(stringvar):
    path = tkFileDialog.askdirectory()
    stringvar.set(path)

def start(quelle, ziel, info):
    info.set("{} > {}".format(quelle.get(), ziel.get()))
    # kopieren starten

def create_frame(root):
    frame = tk.Frame(root)
    quelle = tk.StringVar(frame)
    ziel = tk.StringVar(frame)
    info = tk.StringVar(frame)
    tk.Button(frame, text='Quellverzeichnis', command=partial(ask_directory, quelle)).grid(row=1,column=1)
    tk.Label(frame, textvariable=quelle).grid(row=1, column=2)
    tk.Button(frame, text='Zielverzeichnis', command=partial(ask_directory, ziel)).grid(row=2, column=1)
    tk.Label(frame, textvariable=ziel).grid(row=2, column=2)
    tk.Button(frame, bg="red", text = "Datensicherung starten", command=partial(start, quelle, ziel, info)).grid(row=3, column=1)
    tk.Label(frame, textvariable=info).grid(row=3, column=2)
    return frame

def main():
    root = tk.Tk()
    root.title("Pfadauswahl")
    frame = create_frame(root)
    frame.grid(row=1, column=1)
    root.mainloop()

if __name__ == '__main__':
    main()
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Das ganze mal mit einer Klasse für das UI. Auf "Zauberreien" mit partial habe ich mal bewusst verzichtet, obwohl ich der Instanz eine Funktion als Parameter mitgebe (Python3+):

Code: Alles auswählen

#!/usr/bin/env python3
# ----------------------------------------------------------------------
import tkinter as tk
import tkinter.filedialog

# ----------------------------------------------------------------------
# Logic
# ----------------------------------------------------------------------

def copy_directory_tree(source_path, destination_path):
    print('Source:', source_path)
    print('Destination:', destination_path)

# ----------------------------------------------------------------------
# User interface (tkinter)
# ----------------------------------------------------------------------

class MainFrame(tk.Frame):
    """The application's main-frame. """
    def __init__(self, master, action=copy_directory_tree):
        tk.Frame.__init__(self, master)
        self.action = copy_directory_tree
        self.source = tk.StringVar(self)
        self.destination = tk.StringVar(self)
        self._init_widget()
        
    def _init_widget(self):
        """Initializes this widget. """
        # - - - Elements - - -
        label_src = tk.Label(self, text='Quellverzeichnis')
        entry_src = tk.Entry(self, textvariable=self.source)
        button_src = tk.Button(self, text='...', command=self.ask_src)
        
        label_dst = tk.Label(self, text='Zielverzeichnis')
        entry_dst = tk.Entry(self, textvariable=self.destination)
        button_dst = tk.Button(self, text='...', command=self.ask_dst)
        
        button_start = tk.Button(self, text='Sicherung starten',
            command=self.start_backup)
        
        # - - - Layout - - -
        self.columnconfigure(1, weight=1)  # Second columns grows
        label_src.grid(row=0, column=0, sticky=tk.W)
        entry_src.grid(row=0, column=1, sticky=tk.EW)
        button_src.grid(row=0, column=2)
        
        label_dst.grid(row=1, column=0, sticky=tk.W)
        entry_dst.grid(row=1, column=1, sticky=tk.EW)
        button_dst.grid(row=1, column=2)
        
        button_start.grid(row=2, column=0, columnspan=3)

    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    # Commands
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    def ask_src(self):
        path = tkinter.filedialog.askdirectory()
        self.source.set(path)
        
    def ask_dst(self):
        path = tkinter.filedialog.askdirectory()
        self.destination.set(path)
        
    def start_backup(self):
        self.action(self.source.get(), self.destination.get())
    
# ----------------------------------------------------------------------
# Main idiom
# ----------------------------------------------------------------------

def main():
    """Program's entry-point. """
    root = tk.Tk()
    frame = MainFrame(root)
    frame.pack(fill=tk.X)
    root.mainloop()
    
if __name__ == '__main__':
    main()
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
DB7WN
User
Beiträge: 49
Registriert: Samstag 18. März 2017, 22:11

Das hatte ich befürchtet. Eigentlich wollte ich zum Beginn nur ein Fenster mit drei Buttons programmieren, die recht einfache Dinge ausführen. Jetzt hat der Code schon 80 Zeilen mit einer Menge von für mich fremden Begriffen :shock:
Ich werd mal versuchen mich reinzubeißen.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

So wild ist das nicht. Das einzige neue Sprachkonstrukt, welches ich angewandt hatte, war "class"; den Rest hattest Du bereits verwendet. :wink:

Der Quelltext bläht sich bei GUI-Programmierung in der Regel ordentlich auf.

"Vergiss, was früher Du gelernt". Dieses XPROFAN sieht merkwürdig aus und wirkt wie ein BASIC-Dialekt. Python ist eine recht hohe (hardware-ferne) Programmiersprache, welche - wenn einmal verinnerlicht - viele Dinge mit sehr wenig Quelltext ermöglicht.

Weiterhin viel Erfolg beim Lernen.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
BlackJack

Und einiges von dem Quelltext verschwindet bei moderneren GUI-Rahmenwerken in Datendateien die die GUI beschreiben.

Edit: Ich denke das der Code nicht testet ob der Benutzer auch tatsächlich Verzeichnisse ausgewählt hat, ist ein bisschen problematisch. Dann wird ja die leere Zeichenkette, also das aktuelle Verzeichnis gewählt. Die meisten Desktopumgebungen setzen das auf das Heimatverzeichnis wenn man grafische Programme per Mausklick startet, und das Heimatverzeichnis noch mal in sich selbst zu kopieren ist bestenfalls sinnlos, macht im ungünstigen Fall aber etwas oder alles kaputt, je nach dem wie vorausschauend das kopieren implementiert wurde — oder eben auch nicht.
Benutzeravatar
bwbg
User
Beiträge: 407
Registriert: Mittwoch 23. Januar 2008, 13:35

Recht hast Du. Der Code testet gar nichts. Im Grunde wollte ich lediglich das Einbinden einer "externen" Funktion in einer GUI demonstrieren.
"Du bist der Messias! Und ich muss es wissen, denn ich bin schon einigen gefolgt!"
DB7WN
User
Beiträge: 49
Registriert: Samstag 18. März 2017, 22:11

Ich will das mal so machen, dass die gewählten Verzeichnisse in einer Datei abgespeichert werden. Beim nächsten Aufruf sollen sie gelesen werden und als Voreinstellung dienen.
Für das Kopieren will ich das Windowscommand "robocopy" benutzen. Ich denke, das kann man irgendwie mit pythons "os" verwenden.
Aber da muss ich auch noch ein bischen googeln.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

DB7WN hat geschrieben:Für das Kopieren will ich das Windowscommand "robocopy" benutzen. Ich denke, das kann man irgendwie mit pythons "os" verwenden.
Aber da muss ich auch noch ein bischen googeln.
Für Python Bordmittel könntest Du Dir 'shutil' anschauen, ansonsten 'subprocess' zum ausführen externer Programme.
Antworten