Seite 1 von 1
Variable aus Funktion übergeben
Verfasst: Samstag 18. März 2017, 22:48
von DB7WN
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)
Re: Variable aus Funktion übergeben
Verfasst: Sonntag 19. März 2017, 00:07
von 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.
Re: Variable aus Funktion übergeben
Verfasst: Sonntag 19. März 2017, 11:37
von DB7WN
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()
Re: Variable aus Funktion übergeben
Verfasst: Sonntag 19. März 2017, 12:51
von Sirius3
@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()
Re: Variable aus Funktion übergeben
Verfasst: Sonntag 19. März 2017, 20:01
von bwbg
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()
Re: Variable aus Funktion übergeben
Verfasst: Sonntag 19. März 2017, 21:50
von DB7WN
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
Ich werd mal versuchen mich reinzubeißen.
Re: Variable aus Funktion übergeben
Verfasst: Montag 20. März 2017, 07:18
von bwbg
So wild ist das nicht. Das einzige neue Sprachkonstrukt, welches ich angewandt hatte, war "class"; den Rest hattest Du bereits verwendet.
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.
Re: Variable aus Funktion übergeben
Verfasst: Montag 20. März 2017, 10:22
von 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.
Re: Variable aus Funktion übergeben
Verfasst: Montag 20. März 2017, 10:37
von bwbg
Recht hast Du. Der Code testet gar nichts. Im Grunde wollte ich lediglich das Einbinden einer "externen" Funktion in einer GUI demonstrieren.
Re: Variable aus Funktion übergeben
Verfasst: Montag 20. März 2017, 12:06
von DB7WN
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.
Re: Variable aus Funktion übergeben
Verfasst: Montag 20. März 2017, 12:19
von kbr
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.