Klassen. Attribute, Methoden für eine tkinter GUI? Wozu?

Fragen zu Tkinter.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: da hilft auch keine Fettschrift. Das ist einfach nur Quatsch. Was sollen denn bitte Methoden simulieren? Klassen sind dazu da, um Methoden mit ihren Daten zu bündeln. Das steht in jedem Tutorial im ersten Abschnitt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: da hilft auch keine Fettschrift. Das ist einfach nur Quatsch. Was sollen denn bitte Methoden simulieren? Klassen sind dazu da, um Methoden mit ihren Daten zu bündeln. Das steht in jedem Tutorial im ersten Abschnitt.
Ist doch einfach, wenn ich schreibe:

Code: Alles auswählen

application.init(master)
Dann kann das bedeuten, dass ich die Funktion init im Modul application aufrufe. Wenn ich aber kein extra Modul möchte, kann ich stattdessen auch ein Objekt application anlegen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Man kann das so schreiben, aber das hat genauso wenig Sinn, wie den ganzen anderen Quatsch, den Du hier schreibst und ihn als unumstößliche Wahrheit anpreist. Wenn Du keine Ahnung hast, kannst Du gerne Fragen, aber hör auf damit, Deine kruden Ansichten Anfängern als Lösungen unterzujubeln.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Mit Go gibt es das oder so ähnlich eben auch als Interpreter.
Go ist genau wie C eine kompilierende Sprache. Nur für den Fall, dass du ein anderes Go kennst als ich: ich meine das Go hier https://golang.org/
Ich schreib ja nicht, dass man sie nicht braucht. Da Frage war doch, wofür man sie braucht.
Für tkinter GUI nach deiner Ansicht ja jedenfalls scheinbar nicht. Wie du weiter oben im Thread "eindrucksvoll" demonstriert hast.

@Sirius3: ich glaube ja manchmal, dass Alfons in einem Paralleluniversum Python gelernt hat und das Python da halt nicht dem idomatischen Python entspricht, was wir so programmieren. Ich bin allerdings noch unschlüssig, um dieses Python diesseits oder jenseits meiner Toleranzschwelle liegt ;-)

Gruß, noisefloor
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Man kann das so schreiben, aber das hat genauso wenig Sinn, wie den ganzen anderen Quatsch, den Du hier schreibst und ihn als unumstößliche Wahrheit anpreist. Wenn Du keine Ahnung hast, kannst Du gerne Fragen, aber hör auf damit, Deine kruden Ansichten Anfängern als Lösungen unterzujubeln.
Es macht total viel Sinn, statt viele Klassen in einem Script zu haben, etliche Module zu haben. Statt lang in einem großen Script zu suchen, erinnert man sich meist an den Dateinamen für die GUI Komponente. Und in dieser Datei ist man von vielem anderem Code der nicht dazugehört, nicht abgelenkt und kommt auch nicht auf die Idee irgendwelche Querverweise auf anderes zu implementieren.

Solche Module sind sehr übersichtlich. Ich habe einmal das Programm in Module aufgeteilt:

Code: Alles auswählen

# === GUI Applikation ====

application.py
- application_gui.py
- - lift_frame_gui.py
- - - remote_manager_gui.py
- - - page_one_gui.py
- - - page_two_gui.py

- main_menu_gui.py
- - manager_menu_gui.py
- - hilfe_menu_gui.py

# === Helper Modules =====
tk.py
eventbroker.py
Im Dateimanager kann man seine Module leicht finden - ab alphabetisch oder nach Änderungsdatum. Wer will kann auch den Aufbau, wie oben dargestellt, noch dokumentieren. Ich habe das aber nicht gebraucht, weil man sich doch an die Filenamen erinnert und der Aufbau genaugenommen keine Rolle spielt.

Kommen wir zu den Helper Modulen. Wenn man so etwas hat wie tk.py braucht man nicht immer zwischen tkinter und Tkinter zu unterscheiden, auch läßt sich leicht DynTkInter einbinden.

Ein sehr einfaches Modul ist tk.py:

Code: Alles auswählen

try:
    from tkinter import *
except ImportError:
    from Tkinter import *
Dann sollte auch dabei sein eventbroker.py:

Code: Alles auswählen

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}
 
    def subscribe(self,message_id,callback):
        self._dictionary_[message_id] = callback
 
    def publish(self,message_id,*args,**kwargs):
        self._dictionary_[message_id](*args,**kwargs)
 
eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe
Das ist aber nur die simpelste Ausführung. Aber sicher leicht zu begreifen.

Da man darauf besteht, daß man zuerst sein Programm startet und dann erst die GUI nachlädt, hatt man dann ein Script File.

application.py

Code: Alles auswählen

import tk
import application_gui

def main():
    root = tk.Tk()
    application_gui.init(root)

    # eigener Code

    root.mainloop()
main()
Da lädt man dann die GUI und will anscheinend hinterher noch etwas machen.

Das ist dann die GUI.
application_gui.py

Code: Alles auswählen

import tk
import lift_frame_gui
import main_menu_gui

def init(self):
    # GUI ==============================
    lift_frame = tk.Frame(self,name='lift_frame')
    main_menu = tk.Menu(self,name='main_menu')
    self['menu'] = main_menu
    lift_frame.pack(fill='both', expand=1)

    lift_frame_gui.init(lift_frame)
    main_menu_gui.init(main_menu)
    # END GUI ===========================
Das ist doch sehr übersichtlich. Und man kann sich jetzt auf zusätzliche Codierung konzentrieren. Aber offensichtlich gibt es da nichts zu tun.

Das dürfe bei diesem lift_frame aber anders werden.

lift_frame_gui.py

Code: Alles auswählen

import tk
import remote_manager_gui
import page_one_gui
import page_two_gui
from eventbroker import subscribe,publish

def init(self):

    # GUI ==============================
    self.rowconfigure(0,weight=1)
    self.columnconfigure(0,weight=1)

    remote_manager = tk.Frame(self,name='remote_manager')
    page_one = tk.Frame(self,name='page_one')
    page_two = tk.Frame(self,name='page_two')

    remote_manager.grid(row=0,sticky = 'nsew')
    page_one.grid(row=0,sticky = 'nsew')
    page_two.grid(row=0,sticky = 'nsew')

    remote_manager_gui.init(remote_manager)
    page_one_gui.init(page_one)
    page_two_gui.init(page_two)
    # END GUI ===========================
    
    # CODE ==============================
    frames = {'remote_manager' : remote_manager,
              'page_one' : page_one,
              'page_two' : page_two,
              }

    subscribe('SHOW_FRAME',lambda frame_id, frames = frames: frames[frame_id].lift())

    publish('SHOW_FRAME','remote_manager')
    # END CODE ==========================   
Das ist das Kernstück der Applikation. Man hat drei Frames in einer einzigen grid Zelle und will auf Anforderung den einen oder anderen in den Vordergrund rücken und damit sichtbar machen.

Erreicht wird dies dadurch, dass diese Frames mit einer ID in einem Dictionary erfasst werden. Dafür gibt es einen Message Callback, mit dem eine Funktion aufgerufen wird, die dann den entsprechenden Frame liftet. Diese Funktion ist das lambda im subscribe Callback.

Dann hat man diese drei Frames, die sich auch auf Buttondruck gegenseitig liften. Der Code Part dazu ist relativ einfach.

remote_manager_gui.py

Code: Alles auswählen

import tk
from eventbroker import publish
from functools import partial

def init(self):

    # GUI ==============================
    label =tk.Label(self,text='Willkommen im Remote Manager')
    button_page_one = tk.Button(self,name='button_page_one',text='Benutzereinstellungen', width=18)
    label_2 = tk.Label(self)
    button_2 = tk.Button(self,text='Bildschirmeinstellungen', width=18)
    label_3 = tk.Label(self)
    button_page_two = tk.Button(self,name='button_page_two',text='Servereinstellungen', width=18)
    label_4 = tk.Label(self)

    label.pack(pady=15, padx=25)
    button_page_one.pack()
    label_2.pack(pady=1, padx=25)
    button_2.pack()
    label_3.pack(pady=1, padx=25)
    button_page_two.pack()
    label_4.pack(pady=5, padx=25)
    # END GUI ===========================
    
    # CODE ==============================
    button_page_one.config(command=partial(publish,'SHOW_FRAME','page_one'))
    button_page_two.config(command=partial(publish,'SHOW_FRAME','page_two'))
    # END CODE ==========================  
page_one_gui.py

Code: Alles auswählen

import tk
from eventbroker import publish
from functools import partial

def init(self):

    # GUI ==============================
    label = tk.Label(self,text='Page One!!!', font='Verdana 12')
    button_remote_manager = tk.Button(self,name = 'button_remote_manager',text='Back to Home')
    button_page_two = tk.Button(self,name='button_page_two',text='Page Two')
    label.pack(pady=20, padx=10)
    button_remote_manager.pack()
    button_page_two.pack()    
    # END GUI ===========================

    # CODE ==============================
    button_remote_manager.config(command=partial(publish,'SHOW_FRAME','remote_manager'))
    button_page_two.config(command=partial(publish,'SHOW_FRAME','page_two'))
    # END CODE ==========================    
page_two_gui.py

Code: Alles auswählen

import tk
from eventbroker import publish
from functools import partial

def init(self):

    # GUI ==============================
    label = tk.Label(self,text='Page Two!!!', font='Verdana 12')
    button_remote_manager = tk.Button(self,name = 'button_remote_manager',text='Back to Home')
    button_page_one = tk.Button(self,name='button_page_one',text='Page One')

    label.pack(pady=10, padx=10)
    button_remote_manager.pack()
    button_page_one.pack()
    # END GUI ===========================

    # CODE ==============================
    button_remote_manager.config(command=partial(publish,'SHOW_FRAME','remote_manager'))
    button_page_one.config(command=partial(publish,'SHOW_FRAME','page_one'))
    # END CODE ==========================    
Dann hat man noch das Menü.

main_menu_gui.py

Code: Alles auswählen

import tk
import manager_menu_gui
import hilfe_menu_gui

def init(self):
    # GUI ==============================
    manager_menu = tk.Menu(self,name='manager_menu')
    hilfe_menu = tk.Menu(self,name='hilfe_menu')

    self.add_cascade(menu=manager_menu,label='Manager')
    self.add_cascade(menu=hilfe_menu,label='Hilfe')

    manager_menu_gui.init(manager_menu)
    hilfe_menu_gui.init(hilfe_menu)
    # END GUI ===========================
Es hat zwei Untermenüs und dafür auch zwei Cascaden

Das ist dann das Untermenü mit viel Action.

manager_menu_gui.py

Code: Alles auswählen

from functools import partial
from eventbroker import publish

def init(self):
    # GUI ==============================
    self.config(tearoff=0)
    self.add_command(label='Komplettes Setup')
    self.add_separator()
    self.add_command(label='Benutzereinstellungen')
    self.add_command(label='Bildschirmeinstellungen')
    self.add_command(label='Servereinstellungen')
    self.add_separator()
    self.add_command(label='Beenden')

    remote_manager_index = 0
    page_one_index = 2
    page_two_index = 4
    # END GUI ===========================
    
    # CODE ==============================
    self.entryconfig(remote_manager_index,command=partial(publish,'SHOW_FRAME','remote_manager'))
    self.entryconfig(page_one_index,command=partial(publish,'SHOW_FRAME','page_one'))
    self.entryconfig(page_two_index,command=partial(publish,'SHOW_FRAME','page_two'))
    # END CODE ==========================    
Aber beim anderen tut sich noch nicht viel.

hilfe_menu_gui.py

Code: Alles auswählen

def init(self):
    # GUI ==============================
    self.config(tearoff=0)
    self.add_command(label='Info')
    # END GUI ===========================
Ist doch alles schön übersichtlich und ganz ohne Klassen.

Ach so das war es auch schon, denn in application.py war doch nichts zu tun.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

ich fühle mich gerade mit meiner Vermutung mit dem Paralleluniversum unglaublich bestätigt *SCNR*

Gruß, noisefloor
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

noisefloor hat geschrieben:Hallo,

ich fühle mich gerade mit meiner Vermutung mit dem Paralleluniversum unglaublich bestätigt *SCNR*

Gruß, noisefloor
Nö, das ist nicht das Paralleluniversum. Das Paralleluniversum ist DynTkInter, dort kann man so etwas noch besser machen:

Code: Alles auswählen

import tk
import lift_frame_gui
import main_menu_gui

def init(self):
    # GUI ==============================
    lift_frame = tk.Frame(self,name='lift_frame')
    main_menu = tk.Menu(self,name='main_menu')
    self['menu'] = main_menu
    lift_frame.pack(fill='both', expand=1)

    lift_frame_gui.init(lift_frame)
    main_menu_gui.init(main_menu)
    # END GUI ===========================
In DyntkInter gilt:
- import braucht es nicht
- master braucht es nicht
- daher braucht es keine init Funktion
- widget Referen braucht es oft auch nicht
- und über Modulimport ein init in einem Modul aufrufen braucht es auch nicht. Gibt ja auch kein init dort

Das obige sieht in DynTkInter so aus:

Code: Alles auswählen

Frame('lift_frame',link='lift_frame_gui')
pack(fill='both', expand=1)
Menu('main_menu',link='main_menu_gui').select_menu()
Und da stimmt das Paralleluniversum, denn so etwas ist kein Modul sondern ein Script. Das ist auf derselben Ebene wie das Mainscript, nur parallel dazu und hat auch keine globalen Variablen, sondern nur lokale, auf die, da es keine Funktion ist, keine Funktion zugreifen kann, wenn sie nicht als Parameter übergeben werden. Auf Importiertes kann auch keine Funktion zugreifen, sofern sie nicht selber importiert oder das Importierte als Parameter übergeben wird.

Und so ein Script verhält sich gegenteilig wie ein Modul. Ein Modul importiert man und kann auf dieses zugreifen. Aber wenn man so ein Script lädt, dann kann das ladende Modul nicht auf dieses zugreifen, aber das Script auf das Modul. Die globale Ebene des Scriptes ist nämlich die Ebene des Moduls.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Läßt sich natürlich auch auf zwei Zeilen reduzieren:

Code: Alles auswählen

Frame('lift_frame',link='lift_frame_gui').pack(fill='both', expand=1)
Menu('main_menu',link='main_menu_gui').select_menu()
Nö, das mit den Ebenen wie Mainscript stimmt nicht. So ein Script ist ein lokaler Namensraum innerhalb DynTkInter auf den von außen nur die ladende Funktion mittels locals() zugreifen kann.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

noisefloor hat geschrieben:ich fühle mich gerade mit meiner Vermutung mit dem Paralleluniversum unglaublich bestätigt *SCNR*
Es kommt ja Null darauf an, wie jetzt eine GUI aufgeteilt ist. Man kann sie im Nu ja wieder vereinen.

Dazu benütze man mein DynTkInter - bitte neueste Version.

Dann ändere man tk.py:

Code: Alles auswählen

'''
try:
    from tkinter import *
except ImportError:
    from Tkinter import *
'''
from DynTkInter import *
Dann ändere man application.py:

Code: Alles auswählen

import tk
import application_gui

def main():
    root = tk.Tk()
    application_gui.init(root)

    # eigener Code
    tk.exportApplication('gui.py')
    #root.mainloop()
main()
DynTkInter kennt die Funktion exportApplication. Dann starte man das und sogleich hat man das:

gui.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.lift_frame = LiftFrame(self,name='#0_lift_frame')
        self.main_menu = MainMenu(self,name='#1_main_menu')
        self['menu'] = self.main_menu
        self.lift_frame.pack(expand=1, fill='both')

class LiftFrame(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        # individual grid definition ===========================
        self.rowconfigure(0,weight=1, minsize=0, pad=0)
        self.columnconfigure(0,weight=1, minsize=0, pad=0)
        # widget definitions ===================================
        self.page_one = PageOne(self,name='#2_page_one')
        self.page_one.grid(sticky='nesw', row=0)
        self.page_two = PageTwo(self,name='#3_page_two')
        self.page_two.grid(sticky='nesw', row=0)
        self.remote_manager = RemoteManager(self,name='#4_remote_manager')
        self.remote_manager.grid(sticky='nesw', row=0)

class PageOne(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.label = tk.Label(self,name='#5_label',text='Page One!!!', font='Verdana 12')
        self.button_remote_manager = tk.Button(self,name='#6_button_remote_manager',text='Back to Home')
        self.button_page_two = tk.Button(self,name='#7_button_page_two',text='Page Two')
        self.label.pack(pady=20, padx=10)
        self.button_remote_manager.pack()
        self.button_page_two.pack()

class PageTwo(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.label = tk.Label(self,name='#8_label',text='Page Two!!!', font='Verdana 12')
        self.button_remote_manager = tk.Button(self,name='#9_button_remote_manager',text='Back to Home')
        self.button_page_one = tk.Button(self,name='#10_button_page_one',text='Page One')
        self.label.pack(pady=10, padx=10)
        self.button_remote_manager.pack()
        self.button_page_one.pack()

class RemoteManager(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.label = tk.Label(self,name='#11_label',text='Willkommen im Remote Manager')
        self.button_page_one = tk.Button(self,name='#12_button_page_one',text='Benutzereinstellungen', width=18)
        self.label_2 = tk.Label(self,name='#13_label_2')
        self.button = tk.Button(self,name='#14_button',text='Bildschirmeinstellungen', width=18)
        self.label_3 = tk.Label(self,name='#15_label_3')
        self.button_page_two = tk.Button(self,name='#16_button_page_two',text='Servereinstellungen', width=18)
        self.label_4 = tk.Label(self,name='#17_label_4')
        self.label.pack(pady=15, padx=25)
        self.button_page_one.pack()
        self.label_2.pack(pady=1, padx=25)
        self.button.pack()
        self.label_3.pack(pady=1, padx=25)
        self.button_page_two.pack()
        self.label_4.pack(pady=5, padx=25)

class MainMenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.manager_menu = ManagerMenu(self,name='#19_manager_menu',tearoff=0)
        self.dyntk_name = 'cascade'
        self.add_cascade(menu=self.manager_menu,label='Manager')
        self.hilfe_menu = HilfeMenu(self,name='#21_hilfe_menu',tearoff=0)
        self.dyntk_name = 'cascade'
        self.add_cascade(menu=self.hilfe_menu,label='Hilfe')
        # indexes for entryconfig later
        self.cascade_index = 1
        self.cascade_index = 2

class ManagerMenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.config(tearoff=0)
        # widget definitions ===================================
        self.dyntk_name = 'command'
        self.add_command(label='Komplettes Setup')
        self.dyntk_name = 'separator'
        self.add_separator()
        self.dyntk_name = 'command'
        self.add_command(label='Benutzereinstellungen')
        self.dyntk_name = 'command'
        self.add_command(label='Bildschirmeinstellungen')
        self.dyntk_name = 'command'
        self.add_command(label='Servereinstellungen')
        self.dyntk_name = 'separator'
        self.add_separator()
        self.dyntk_name = 'command'
        self.add_command(label='Beenden')
        # indexes for entryconfig later
        self.command_index = 0
        self.separator_index = 1
        self.command_index = 2
        self.command_index = 3
        self.command_index = 4
        self.separator_index = 5
        self.command_index = 6

class HilfeMenu(tk.Menu):

    def __init__(self,master,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.config(tearoff=0)
        # widget definitions ===================================
        self.dyntk_name = 'command'
        self.add_command(label='Info')
        # indexes for entryconfig later
        self.command_index = 0

if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Es kommt also wirklich nicht darauf an, wie man seine GUI schreibt. Hauptsache es sind Namen drin, damit man sich nachher auch gut zurechtfindet. Leider kann der Code nicht mitgeneriert werden.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Attribute sollte man nehmen. Widgets sind ja Objekte kann man also Attribute hinzufügen.

Wenn man seinen Code schreibt ohne Attribute zu nehmen und wenn man dann seine GUI in Klassen konvertiert, dann paßt nämlich der Code nicht mehr zu den generierten Attributen.

Wenn man allerdings nicht vor hat, seinen Code in Klassen zu konvertieren, dann kann man das auch sein lassen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ohne Klassen. Attribute, Methoden und Namen hat man ein Höchstmaß an Kapselung!

Die Gliederung in Module ist angebracht bei komplexen großen GUIs. Da kann man Klassen verwenden oder auch nicht. Denn ein Widget anlegen und dann eine init Funktion in einem anderen Modul aufrufen geht genauso gut. Und bietet einen großen Vorteil.

Es existiert eine reine tkinter GUI ohne zusätzlich Verweise über Attributnamen und Widgetnamen - die sollte man dann auch nicht nehmen. Über einen benannten Objektbaum zugreifen kann man dann nicht. Über die Children Liste von tkinter zugreifen, bringt auch nichts, da mit nichtssagenden Objektreferenzen nichts anzufangen ist. Wenn keine zusätzlichen Attribute verwendet werden und auch keine Methoden sind auch darüber keine Zugriffe von außen möglich. Denn auf das Innere von Closures gibt es nur Zugriffe mittels im Closure vereinbarten Callbacks, also Command Callbacks, Event Callbacks, Message Callbacks.

Damit ist dann ein Höchstmaß an Kapselung erreicht und damit ist kreuz und quer Programmierung nicht mehr möglich - sondern Aufrufe gibt es dann nur mehr über wohl definierte Message Interfaces. Eine komplexe GUI sollte dann auch ohne Probleme leicht zu programmieren sein.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Natürlich spricht nichts dagegen im inneren der Closures auch Klassen einzusetzen oder statt der Closure ein referenzloses Objekt, denn da kommt man auch von außen nicht ran.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Ich bin aber schon sehr frühzeitig darauf gekommen, wie man in Python gut kapseln kann. Wenn man Methoden von Objekten in Callbacks referenziert und das Objekt selber nirgends, dann bleibt das Objekt im Speicher aber Zugriff von außen gibt es keinen. Die Referenz auf den Callback mag man zwar herausbekommen aber den kann man auch auf die spezifizierte Art und Weise aufrufen das Objekt selber bekommt man aber nicht. Und wenn man dann den Callback löscht, etwa im Eventbroker für die Message einen anderen einträgt, ist das Objekt weg im Garbage Collektor. Und wenn man die Source für die Erzeugung des Objektes nicht in einem Modul hat, sondern das Objekt durch Scriptausführung erzeugt hat - aber nicht Mainscript, ist auch vom Code im Speicher nichts mehr da. Bei einem Widget wäre natürlich das Widget noch da, solange man kein destroy ausführt.

Ich finde Objekte gut, auf die man nicht zugreifen kann und die man spurlos beseitigen kann und den Code im Speicher dazu auch.

Man könnte also eine GUI schreiben mit zigtausenden von Screens mit soviel Python Code, wie die Festplatte hergibt. Aber wenn man immer nur einen Screen im Speicher hat, ist alles kein Problem.

Ist doch ganz einfach: eine lokal aufgerufene Funktion oder Klasse wird durch Bindung einer ihrer Mehoden oder Funktionen an einen Callback zu einem weiter bestehenden Objekt. Auch eine Closure ist dann so ein Objekt.
Zuletzt geändert von Alfons Mittelmeyer am Dienstag 22. August 2017, 12:22, insgesamt 1-mal geändert.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Sirius3 hat geschrieben:Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Ich betrachte Programmieren auch als Kunst. Da gibt es realistische, abstrakte bis abartige Werke. Ich schlage vor in unserem Forum ein neues Subforum zu eröffnet, welches als Gallerie für abweichend Werke dient. Bin mir sicher, dass sich jenes Subforum innert kürzester Zeit mit künstlerisch Beiträgen aufblähen wird.

Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

wuf hat geschrieben:Ich betrachte Programmieren auch als Kunst. Da gibt es realistische, abstrakte bis abartige Werke.
Gruss wuf :wink:
Findest Du meine Ideen abartig? Ich habe doch einen Weg gefunden wie man gut kapseln kann. Dafür kann man auch Klassen einsetzen. Die Methoden und Attribute von Klassen sind öffentlich. Aber wenn das Objekt nicht öffentlich ist, sind sie es auch nicht und so kann man gut kapseln:

Code: Alles auswählen

# statt
self.my_frame = MyFrame(self,**kwargs)
self.my_frame.pack()

# schreibe man einfach
MyFrame(self,**kwargs).pack()

# und schon hat man gut gekapselt
Das kann man nicht nur bei Widget Klassen machen, sondern auch bei x-beliebigen Klassen. Bei Funktionen geht das auch, aber da muß man eine eventuelle Vererbung von einer Basisfunktion selber implementieren. Bei Vererbung sollte man daher besser Klassen nehmen statt Funktionen.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Findest Du meine Ideen abartig? Ich habe doch einen Weg gefunden wie man gut kapseln kann.
1. Genie und Wahnsinn liegen dicht beiander.
2. Wahre Genies werden zu Lebzeiten oft verkannt -> es gibt also noch die Hoffnung der späten Ehre für dich :-)

Gruß, noisefloor
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

noisefloor hat geschrieben:2. Wahre Genies werden zu Lebzeiten oft verkannt -> es gibt also noch die Hoffnung der späten Ehre für dich :-)
Wäre schön, übrigens ist kaum ein Unterschied zwischen Klassen und Funktionen, denn auch Funktionen können Attribute haben:

Code: Alles auswählen

def function(*args,**kwargs):

    class Attributes:
        pass

    self = Attributes()

    self.a = 5

    def output():
        print(self.a)

    def set_a(value):
        self.a = value

    # statt __init__(self), braucht man bei Funktionen nicht
    output()
    set_a(8)
    output()
    
function()   
Und das Schöne daran ist, dass diese Attribute nicht öffentlich sind. Man kann also auch in Python gut kapseln.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Funktionen kann man auch instanzieren. Im nachfolgenden Beispiel legen wir zwei Instanzen einer Funktion an und testen deren Interface

Code: Alles auswählen

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}
 
    def subscribe(self,message_id,callback):
        self._dictionary_[message_id] = callback
 
    def publish(self,message_id,*args,**kwargs):
        self._dictionary_[message_id](*args,**kwargs)
 
eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe

def function(function_id,message_id):

    def output(message):
        print(function_id,message)

    # callback interface
    subscribe(message_id,output)        
   

# hier legen wir zwei Instanzen der Funktion an
function('Funktion 1','func1')
function('Funktion 2','func2')

# hier testen wir deren Interface
publish('func1','Guten Morgen')
publish('func2','Gute Nacht')
Zuletzt geändert von Alfons Mittelmeyer am Dienstag 22. August 2017, 15:16, insgesamt 1-mal geändert.
BlackJack

@Alfons Mittelmeyer: Du verwechselst hier Kapselung mit Zugriffsschutz. Kapseln kann man genau so gut mit Klassen, *und* das ist der in Python vorgesehene Weg. Wenn man mehr Zugriffsschutz will, dann will man eine andere Programmiersprache verwenden.

Das `function()`-Beispiel macht keinen Sinn. Da ist nichts wofür man eine Klasse bräuchte.

Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche, noch in Unit-Tests.
Antworten