Was ist eine globale Variable und warum soll man sie nicht benützen?

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.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Alfons Mittelmeyer: zum Thema globaler Zustand ist Sirius3s Antwort ausführlich und gut verständlich.
Alfons Mittelmeyer hat geschrieben:Die weitere Frage, was ist eine Variable?
Nun halte dich fest oder setze Dich: Python kennt gar keine Variable.

Python kennt nur Objekte. Und Typen von Objekten. Diese können mutable, immutable oder callable sein. Und Python kennt Namensräume, die wiederum Objekte sind. Sowie Referenzen auf Objekte.

Sobald dies verinnerlicht ist steht einer klaren, kompakten, sauberen und flüssig lesbaren pythonischen Programmierung nichts mehr im Wege. Und dann gibt es auch keine Entschuldigungen mehr, es anders zu machen; jedenfalls nicht mit Python.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

pillmuncher hat geschrieben: wie oft man (fast) gar keinen Zustand braucht um ein nützliches Programm zu schreiben.
Ja es geht um das fast. Weil es da immer wieder welche gibt, die darauf bestehen, dass man globale Variablen gar nicht und auf gar keinen Fall benützen soll.

Und in Python selber gibt es globale Zustände. So verwaltet etwa import Zustände in dieser Art:

Code: Alles auswählen

_imported = {}

def _module_load(filename):
    # load the module and create something like an instance, for example:
    exec(compile(open(filename, "r").read(), filename, 'exec'))
    return locals()['Access']()

def module_import(filename):
    if filename in _imported:
        return _imported[filename]
    else
        module_instance = _module_load(filename)
        _imported[filename] = module_instance
        return module_instance
Nach dem ersten import erhält man mit weiteren imports dieselbe Referenz, also keine neue Instanz des Moduls. Der import merkt sich also einen globalen Zustand und liefert so das Gemerkte.

Ist das falsch?

Ach so, diesen Code würde man nicht benützen, weil es ja schon import gibt. Für mich interessant ist dagegen die Funktion _module_load, die ich anders benannt habe und gerne benütze.
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es ist erstaunlich zu sehen, wie sehr du dich weigerst dein Weltbild an neue Erkenntnisse anzupassen, und stattdessen per mal mehr mal weniger gelungenem Gegenbeispielen versuchst zu belegen, das was wir dir hier erklären, nicht stimmt.

Ja, es gibt globalen zustand. Python kann das, mehr noch, braucht das. ZB für Code-Objekte (Also Module etc), Konstanten. Und ja, es ist auch gelegentlich sinnvoll Dinge zu cachen. Das ist so sinnvoll, man hat ein Lehnwort dafür eingeführt.

Nur ist der Umkehrschluss nicht richtig. Nur weil es etwas gibt, ist es nicht das Mittel der Wahl für alles. Jedes Stück Code das du hier gepostet hast, in dem eine lokale Variable vorkommt, benutzt offensichtlich keinen globalen Zustand für alles, wofür das möglich wäre. Deiner verqueren Logik nach darf das aber nicht sein, denn man kann ja global, also muss auch. Also denn. Ich will hier nie wieder eine lokale Variable sehen von dir. Falls du sie doch gebrauchst, erklärst du das hoffentlich genauso ausufernd, wie du das hier von uns verlangst.
BlackJack

@Alfons Mittelmeyer: Es geht nicht um das fast. Das man Zustände braucht bedeutet nicht das die global sein müssen. Eben das sollten sie nicht.

Das Importbeispiel ist kein gutes weil es sich bei den Modulen um Konstanten handelt — sofern sich der Programmierer daran hält globalen Zustand zu vermeiden, was er ja sollte. Es sollte keinen Unterschied machen ob ein Modul mehr als einmal importiert wurde, oder ob es beim ersten Import gecached wurde.

Und natürlich gibt es Ausnahmen, die wurden aber hoffentlich von Leuten geschrieben die sich der Problematiken bewusst sind, also nicht fragen was Variablen und Konstanten sind.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Es geht nicht um das fast. Das man Zustände braucht bedeutet nicht das die global sein müssen. Eben das sollten sie nicht.
Unbedingt global muss natürlich nichts sein. Es ginge auch anders, aber ist anders immer sinnvoll? Ein größeres Programm besteht aus Komponenten, diese Komponenten müssen miteinander kommunizieren, Ein Fahrzeug hat dafür Bussysteme, etwa MOST Bus, CAN Bus, LIN Bus, Flexray.

Soll man nach deiner Auffassung allen Objekten, bei denen Unterobjekte diese Bussysteme eventuell benötigen, die Zugriffsobjekte auf diese Bussysteme als Parameter übergeben und sie von Ebene zu Ebene weiterreichen?

Denn genau darum geht es mir, nämlich ob diese zentralen Signalsysteme global zur Verfügung stehen dürfen oder ob man sie überall als Parameter mit übergeben soll?
__deets__
User
Beiträge: 14542
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ja. Wo immer das geht. Es ist oft ein Zeichen vermurksten Designs wenn alle alles kennen müssen. Dieses durchreichen stoppt also recht schnell, wenn man vernünftig arbeitet.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

__deets__ hat geschrieben:Ja. Wo immer das geht. Es ist oft ein Zeichen vermurksten Designs wenn alle alles kennen müssen. Dieses durchreichen stoppt also recht schnell, wenn man vernünftig arbeitet.
Also für eine tkinter Gui habe ich jetzt die Lösung gefunden. Es reicht, wenn man so ein Signalsystem in der obersten Ebene gekapselt hat statt es global zu haben oder es überall bei der Instanzierung von Objekten als Parameter mit weiterzureichen. Mit der folgenden Funktion - das ist jetzt keine Variable - kann man dann auf das Root Objekt zugreifen:

Code: Alles auswählen

def root(gui_element):
    root = gui_element   
    while root.master:
        root = root.master
    return root
Wenn man lang genug sucht, findet man doch eine Lösung, die nicht global ist.

PS: alle sollen nicht alles kennen, aber das zentrale Kommunikationssystem sollen alle kennen. Die Telefonnummern von jedem sollen nicht alle kennen, aber das Telefon soll man schon kennen und die Telefonnummern von seinen Freunden und Bekannten.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Alfons Mittelmeyer hat geschrieben: [...] ob diese zentralen Signalsysteme global zur Verfügung stehen dürfen oder ob man sie überall als Parameter mit übergeben soll?
Das kommt darauf an, welche Architektur so ein Programm hat. Man könnte sowas mittels Dependency Injection lösen, also mittels Parameter, die man dann überall mitschleift. Man könnte daraus ein Parameter-Objekt machen, das diese ganzen Sub-Systeme zu einer einzigen Struktur zusammen fasst. Damit würde man aber den Grad der Isolation abschwächen, da dann jede Funktion, die dieses Objekt übergeben bekommt, auf jedes beliebige Sub-System zugreifen könnte. Wenn man genügend Disziplin besitzt, muss das aber kein Problem sein.

Letzteres hatte ich mal testweise in Clojure umgesetzt. Dabei repräsentierten Components (Services) die Aussenwelt, also zB. Datenbanken, und aller veränderbare Zustand war ausschließlich dort zuhause. Die Geschäftslogik war rein funktional und eventbasiert. Das muss man sich so vorstellen: ein Command (ein dummes Dictionary, das ein Type-Tag und alle relevanten Daten enthält) wird empfangen, dazu wird eine passende Handler-Funktion gesucht und aufgerufen. Diese Funktion erzeugt dann aus den Daten im Command und dem aktuellen Zustand der Welt eine Liste von Events, die sie als Ergebnis zurückgibt. Sonst macht diese Handler-Funktion nichts, dh., sie ändert nicht den Zustand der Welt. Die Ergebnis-Events werden dann über einen Eventbus verschickt. Event-Handler können Events vom Bus abonnieren und werden aufgerufen, sobald ein entsprechender Event eintritt. Diese Handler erst dürfen den Zustand der Welt verändern, indem sie zB. Daten in eine Datenbank schreiben.

Zusätzlich habe ich Dependency Injection in den Command-Handlern mittels der Reader-Monade realisiert und diese zur Fehlerbehandlung in einen Error-Monad-Transformer gewickelt. Das sieht dann etwa so aus:[codebox=clojure file=Unbenannt.txt](def-command ::customer/place-order
:req [::customer/id
::order/id])

(def-failure ::customer/has-given-no-address
:req [::customer/id])

(def-failure ::customer/cart-is-empty
:req [::customer/id])

(def-failure ::customer/has-selected-no-payment-method
:req [::customer/id])


(defn subscriptions
[component]

{::order/placed
(store-in (:repository component) ::customer/id)

::customer/place-order
(fn [{customer-id ::customer/id
order-id ::order/id}]
(within (boundary component ::customer/shop)
(fail-if-exists ::order/id order-id)
customer <- (get-entity ::customer/id customer-id)
(mdo-await*
(mwhen (-> @customer ::customer/cart empty?)
(fail-with ::customer/cart-is-empty
::customer/id customer-id))
(mwhen (-> @customer ::customer/address nil?)
(fail-with ::customer/has-given-no-address
::customer/id customer-id))
(mwhen (-> @customer ::customer/payment-method nil?)
(fail-with ::customer/has-selected-no-payment-method
::customer/id customer-id)))
(return [(event ::order/placed
::customer/id customer-id
::order/id order-id
::order/items (@customer ::customer/cart)
::order/address (@customer ::customer/address)
::order/payment-method (@customer ::customer/payment-method))
(event ::customer/cart-cleared
::customer/id customer-id)])))})[/code]
In der Handler-Funktion für ::command/place-order werden nirgends explizit Repositories, Datenbanken, etc. erwähnt, obwohl deren Informationen dort verwendet werden. Wie monadische Funktionen funktionieren kann ich allerdings hier nicht erklären. Monaden kann man IMO überhaupt nicht erklären. Man kann sie auch nicht verstehen. Man kann sich nur an sie gewöhnen.

Bild
In specifications, Murphy's Law supersedes Ohm's.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

pillmuncher hat geschrieben:
Alfons Mittelmeyer hat geschrieben: [...] ob diese zentralen Signalsysteme global zur Verfügung stehen dürfen oder ob man sie überall als Parameter mit übergeben soll?
Das kommt darauf an, welche Architektur so ein Programm hat. Man könnte sowas mittels Dependency Injection lösen, also mittels Parameter, die man dann überall mitschleift. Man könnte daraus ein Parameter-Objekt machen, das diese ganzen Sub-Systeme zu einer einzigen Struktur zusammen fasst. Damit würde man aber den Grad der Isolation abschwächen, da dann jede Funktion, die dieses Objekt übergeben bekommt, auf jedes beliebige Sub-System zugreifen könnte. Wenn man genügend Disziplin besitzt, muss das aber kein Problem sein.
Dass jeder auf alles zugreifen kann, wird bei komplexen Systemen unterbunden. Da wird das Bussystem von der Admistration so konfiguriert, dass bestimmte Hardware- oder Softwarekomponenten nur solche Messages mit den Funktionsblöcken bekommen, für die sie zuständig sind. Teilkomponenten davon sollen dann wiederum nur ein Subset bearbeiten und initialisieren dann nur Zugriffe für ihre Funktionsblöcke - das ist so etwas, wie die Vorwahlen für die Signale. Dass man auch andere Funktionsblöcke oder andere Messages verwendet, die nichts mit den Aufgaben der Teilkomponente zu tun haben, wird durch Codereview unterbunden. Außerdem würde man das an Trace Messages sehen, welche den Messageaustausch und die Registrierung für Funktionsblöcke mitprotokollieren. Man kann also nicht zugreifen, worauf man will, denn es ist genau spezifiziert, was ein Softwaremodul zu tun hat.

PS: für eine kleinere Anwendung ist wohl der Aufwand für eine Unterteilung in Vorwahlen und Messagenummer im Vorwahlbereich und Filterung nach erlaubten Vorwahlen unnötig.
BlackJack

@Alfons Mittelmeyer: Man muss dieses Rad nicht neu erfinden, dafür haben Widgets bereits eine Methode. Wobei man die selten bis gar nicht brauchen wird oder sollte. Beliebige Widgets sollten nicht am Toplevelfenster manipulieren wollen. Wenn das jeder machen würde kämen die sich sicher schnell in die Quere.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Man muss dieses Rad nicht neu erfinden, dafür haben Widgets bereits eine Methode. Wobei man die selten bis gar nicht brauchen wird oder sollte. Beliebige Widgets sollten nicht am Toplevelfenster manipulieren wollen. Wenn das jeder machen würde kämen die sich sicher schnell in die Quere.
Ach so, diese Methode winfo_toplevel funktioniert nicht. Für ein Menü ist, kommt dabei das Menü heraus und nicht das Anwendungsfenster.

Es geht nicht um Widgets es geht um Interkommunikation. Eine Software könnte in verschiedene Teilapplikationen unterteilt sein - das nenne ich einmal Applications. Da könnte es eine Kommunikation innerhalb einer Applikation und auch zwischen den Applikationen geben. Und beliebige Widgets sollten natürlich nicht am Toplevelfenster manipulieren. Es kann natürlich sein, und wird auch meist so sein, dass ein Druck auf einen Menübutton Auswirkungen auf Teile der Gui hat. Wo diese Gui Teile sind, geht dem Menübutton meiner Meinung nach gar nichts an, der sollte über den inneren Aufbau der Gui gar nichts wissen.

Es geht darum, dass der Druck auf den Menübutton (command) das veranlaßt, was er soll, ohne dass die Callbackroutine für den Menübutton kreuz und quer in der Gui umeinandfummelt. Wie gesagt, der Gui Aufbau soll für das Menü verborgen bleiben!!!

Und das geht jetzt auch ohne globale Variablen. Man definiert einfach, dass eine Applikation bestimmte Attribute und Methoden hat und dass Bestandteile der Applikation darauf zugreifen können sollen, sofern das benötigt wird. Das sind dann keine globalen Variablen, sondern applikationsweit existierende Objekte. Die Referenz auf die Applikationsebene, das kann die Ebene des Anwendungsfensters sein, oder auch darüber außerhalb der Gui oder auch darunter für einen Teil der Gui, wird dann jeweils bei der Instanzierung von Objekten übergeben.

Und nicht jedes Objekt kann auf alles zugreifen, sondern nur die Objekte, denen man die Referenz für das Signalsystem übergeben hat. Das Signalsystem behandelt auch nur gewisse Meldungen, von einem Zugriff auf Alles kann da keine Rede sein.

OK, also am Toplevelfenster haben die widgets nichts zu machen, dann ist das so:

Code: Alles auswählen

import tkinter as tk

class Application():
    def __init__(self):
        self.callbacks = {}

class Gui(tk.Tk):
    def __init__(self,application):

        # Application
        self.application = application

        tk.Tk.__init__(self)
        self.attributes('-zoomed', True)
        self.grid_columnconfigure(0, weight=1)

        # Frames
        self.one = ExampleClassOne(self)
        self.two = ExampleClassTwo(self)
 
        # menubar
        self['menu'] = MenuBar(self,self.application)
 
        # register callbacks
        self.application.callbacks['example_method_one'] = self.example_method_one
        self.application.callbacks['example_method_two'] = self.example_method_two

    def example_method_one(self):
        self.two.grid_forget()
        self.one.grid()
 
    def example_method_two(self):
        self.one.grid_forget()
        self.two.grid()
 
 
class ExampleClassOne(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        one = tk.Label(master=self,
                       text='Test 1',
                       font=('Monospace', 20))
        one.grid(row=0)
 
 
class ExampleClassTwo(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master)
        two = tk.Label(master=self,
                       text='Test 2\nTest 2\nTest 2')
 
        two.grid(row=0)
 
 
class MenuBar(tk.Menu):
    def __init__(self, master=None, application=None):
        self.application=application
        tk.Menu.__init__(self, master)
 
        # submenu
        self.file = tk.Menu(master)
        self.example = tk.Menu(master)
 
        # submenu cascades
        self.add_cascade(label='Datei', menu=self.file)
        self.add_cascade(label='Beispiel', menu=self.example)
 
        # submenu file
        self.file.add_command(label='Beenden', command=self.quit)
 
        # submenu example
        self.example.add_command(label='Beispiel 1', command=self.example_method_one)
        self.example.add_command(label='Beispiel 2', command=self.example_method_two)

    def example_method_one(self):
        self.application.callbacks['example_method_one']()
 
    def example_method_two(self):
        self.application.callbacks['example_method_two']()
 
 
def main():
    Gui(Application()).mainloop()
 
 
if __name__ == '__main__':
    main()
Abgewandeltes Beispiel von Schwarzer Wolf aus dem tkinter Forum: viewtopic.php?f=18&t=40332
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@Alfons Mittelmeyer:
Globaler Zustand meint eigentlich das Einbringen eines äusseren Zustandes in etwas Inneres. Das Innere kann in Python der Kontext eines Modul, einer Klasse oder einer Funktion/Methode sein. Dem begegnest Du allerorten - Modulimporte, Funktion- oder Klassendefinitionen etc. Diese sind in der Regel konstant, d.h. ein `from a import A` gibt Dir immer das gleiche unveränderte Objekt hinter `A`, ein `do_something(); do_something(); do_something();` sollte 3x auf das gleiche unveränderte Funktionsobjekt zeigen.

Probleme bekommst Du, wenn der äussere Zustand veränderlich ist bzw. prinzipiell einen undefinierten Zustand haben kann - dann sind alle weiteren Aktionen prinzipiell auch undefiniert. Als überspitztes Bsp - was würdest Du sagen, wenn `print "Hello World"` plötzlich Deine Festplatte formatiert? Daher gilt grundsätzlich, dass der äussere Zustand so gut es eben geht wohl definiert sein soll - das geht verlässlich nur mit Konstanten. Weil nur konstanter äusserer Zustand nicht zielführend ist, hat man sich Interfaces überlegt, welche bessere Kapselung schaffen - z.B. Funktionen mit Parametern und Rückgabewerten. Bedenke, dass jedwedes veränderliche (mutuable) Objekt als Parameter einer Funktion immer noch ein globaler Zustand ist, und alle Probleme erbt, wenn das Objekt in der Funktion modifiziert wird (nicht reentrant). Diese "Verletzung" wird allerdings hingenommen, da ein Objekt für sich eine Kapselung von Code und Daten (Zustand) darstellt und ein solches Vorgehen den Kontrollfluß nachvollziehbar hält - das nennt man dann OOP.

In low level Programmierung funktioniert das nicht mehr selbstverständlich, da Interrupthandler auch mal den `this`-Pointer in C++ verbiegen können, häufigste Folge dürfte ein segfault sein, ggf. auch ein Angriff auf das System. Damit wird offensichtlich, dass auch objektweite Sicht- und Modifizierbarkeit schon ein Problem darstellen kann. C hat mit lokalen `static` Variablen noch eine Besonderheit, die es zu vermeiden gilt - der Wert ist ist zwar nur lokal sicht- und veränderbar, "klebt" aber im Speicher und steht bei der nächsten Ausführung zur Verfügung. Daher ist es ein versteckter globaler Zustand.

Zum Glück werden diese Probleme von der libc und Python vor uns versteckt - übrig bleiben die klassischen veränderlichen globals in Python. Und da geht es am ehsten um Nachvollziehbarkeit des Kontrollflusses.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@jerch: danke für Deine Auskünfte. Es geht als nicht darum, ob etwas modul global ist oder zwar gekapselt ist, aber dennoch applikationsweit verfügbar ist. Das kommt eigentlich fast auf dasselbe hinaus. Es kommt darauf an, dass sich alles spezifizert verhält. Und Globales möglichst konstant ist.

Wenn man einer Variablen nur einmal einen Wert zuweist und diesen nicht verändert, dann ist das eine Konstante.
Wenn man aber einem dictionary einen Wert zuweist, etwa english['hallo'] = 'hello', und diesen nicht verändert, kann man auch von einer Konstante sprechen.

Wenn man später das dictionary erwitert, etwa mit english['Katze'] = 'cat', dann hat man den Wert für 'hallo' nicht verändert. Man hat nur seine Konstantendefinitionen erweitert.

Es ist allso völlig fehl am Platze, wenn dann von einigen das Geschrei losgeht, dass wenn man das Dictionary erweitert, es zu einer Variablen wird, und das soll man nicht tun.

Ich hoffe, dass man in der Lage ist, zu erkennen, dass die Erweiterung von Konstantendefinitionen, nicht daraus Variablen macht.
Sirius3
User
Beiträge: 17753
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: ich weiß, auf welche Spizfindigkeiten Du hier anspielst, aber wenn in einer Funktion ein globales Wörterbuch verändert wird, ist eben nicht sichergestellt, dass das danach Konstant bleibt. Bei Deinem Beispiel ist sowohl Katze als auch cat eine Konstante und daher auch die Zuweisung eine Konstante. Nimmt man aber statt "cat" self.example_method_one ist das keine Konstante mehr, damit kann man auch nicht behaupten, das Wörterbuch wäre konstant. Es gibt Fälle, wo man dynamisch Konstanten erzeugen könnte, aber dann sieht der Code dazu so aus:

Code: Alles auswählen

ENGLISH = read_dictionary("english.txt")
Das heißt, die Konstante ENGLISH bekommt *einmal* einen Wert, der sich aber nach der Definition nie wieder ändert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Alfons Mittelmeyer hat geschrieben:Wenn man aber einem dictionary einen Wert zuweist, etwa english['hallo'] = 'hello', und diesen nicht verändert, kann man auch von einer Konstante sprechen.
Ja, das Item ['hallo'] ist dann konstant. Da es in Python keine echten Konstanten gibt, geht es nur per Konvention, den Wert nicht mehr zu ändern.
Alfons Mittelmeyer hat geschrieben:Wenn man später das dictionary erwitert, etwa mit english['Katze'] = 'cat', dann hat man den Wert für 'hallo' nicht verändert. Man hat nur seine Konstantendefinitionen erweitert.
Da wird es tricky, ['hallo'] ist konstant, der Container `english` aber nicht mehr. Das kann dann zu Problemen führen, wenn sich nachfolgender Code auf das (Nicht-)Vorhandensein von Items verlässt, ohne es erneut zu prüfen. Das ist ein typisches Problem, was man sich mit einem "world-object" einhandelt.
Ürbigens ist es völlig legitim, Konstanten schrittweise zu initialisieren, wenn es sich anbietet. Das sollte aber vor der ersten Benutzung im Hauptkontrollfluß fertig sein, also nix nachträglich ranfummeln:

Code: Alles auswählen

# init - Konstanten
USERS = ['A', 'B']
if xy:
    USERS.append('C')  # hier ok

# Hauptkontrollfluß
def main():
    ....# macht was mit USER
    USER.append(D)  # hier nicht ok
    ....# macht was mit USER - Achtung USER != USER
Falls es unvermeidlich ist (meist wegen falschem Design), dann muss sowas unbedingt dokumentiert werden, sodass alle (auch man selbst) drüber stolpert. Für Bibliothekscode zur Benutzung durch Andere ist sowas generell "verboten".
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben:Übrigens ist es völlig legitim, Konstanten schrittweise zu initialisieren, wenn es sich anbietet. Das sollte aber vor der ersten Benutzung im Hauptkontrollfluß fertig sein, also nix nachträglich ranfummeln
Was ist der Hauptkontrollfluß?

Ist dies das, was bei tkinter in der mainloop geschieht? Vorher, beim Aufbau der Gui - vor mainloop - sollen Verweise auf bestimmte Methoden bestimmter Objekte in das Dictionary eingetragen werden, unmittelbar nach der Erzeugung der Objekte.
BlackJack

Damit ist der Code gemeint der auf Modulebene ausgeführt wird wenn das Modul importiert wird und der nur Konstanten, Funktionen, und Klassen definiert.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Alfons Mittelmeyer hat geschrieben:Ist dies das, was bei tkinter in der mainloop geschieht? Vorher, beim Aufbau der Gui - vor mainloop - sollen Verweise auf bestimmte Methoden bestimmter Objekte in das Dictionary eingetragen werden, unmittelbar nach der Erzeugung der Objekte.
Mit tkinter kenne ich mich zu wenig aus, daher kann ich dazu nicht viel sagen.

Eine andere populäre GUI-Bibliothek ist Qt/PyQt. Diesem merkt man die Herkunft von C++ teilweise noch an, da es eine ziemlich strikte Trennung von Definitionen und Ausführungslogik möchte:

Code: Alles auswählen

# Definitionsteil
import ...

SOME_CONST = ...

class XY(QObject):
    ...

def functionXY():
    ...

# Einsprung für Ausführung (als Hauptskript)
# ab hier keine neuen Klassen, Konstanten oder Funktionen mehr,
# sondern nur noch Benutzung des Definierten (gilt auch für modulweite Importe)
if __name__ == '__main__':
    app = QApplication()
    xy = XY()
    ...
Was man hin und wieder sieht ist ähnlich wie (bitte nicht nachmachen):

Code: Alles auswählen

import ...

app = QApplication()

class XY(QObject):
    ....
    def do_something_with_SOME_GLOBAL(self):
        global SOME_GLOBAL
        ...

xy = XY()
SOME_GLOBAL = ...
import z
xy.do_something_with_SOME_GLOBAL(z.foo)
Zweiteres verursacht unbestimmtes Verhalten, da Qt es nicht mag, wenn Klassen nach `QApplication` erstellt werden. Python an sich verbietet das nicht. Hintergrund bei Qt sind übrigens globale Zustände, bei denen die Reihenfolge der Abarbeitung plötzlich wichtig wird ;). (In C++ selbst ist dies kein Problem, da zur Laufzeit alle Typen/Klassen bekannt sind.)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

jerch hat geschrieben:[Ürbigens ist es völlig legitim, Konstanten schrittweise zu initialisieren, wenn es sich anbietet. Das sollte aber vor der ersten Benutzung im Hauptkontrollfluß fertig sein, also nix nachträglich ranfummeln
Es geht nicht um ranfummeln. Fummeln wäre, wenn die Schnittstellen nicht für nachträgliche Registrierungen ausgelegt wären. Wenn die Schnittstellen aber dafür ausgelegt sind, dann ist es ein spezifiziertes Verhalten.

Ein Temperatursensor darf ruhig die Außentemperatur senden, auch wenn noch keine Anzeige dafür aktiv ist. Er darf aber natürlich keine noch undefinierte Callbackfunktion aufrufen.

Durch entsprechende Interfaces kann man dafür sorgen, dass es keine undefinierten Zustände gibt

Ein einfaches dictionary würde für einfache überschaubare Applikationen genügen.
Jedoch für größere Applikationen wäre dieses ungeeignet

Code: Alles auswählen

# Für eine kleine Anwendungen mit genau einer Anzeige durchaus ausreichend und sinnvoll
vehicle.statusbus['aussentemperatur'](23)
# Falsch für komplexe Anwendungden, denn dieses würde crashen, wenn noch keine Callbackfunktion registriert ist
# ausserdem funktioniert das auch nur bei genau einer Anzeige

# Auch für komplexe Anwendungen geeignet
vehicle.statusbus.broadcast("aussentemperatur",23)
# Richtig, alle dafür registrierte Anzeigen erhalten die Nachricht
# Ist noch keine Anzeige registriert, dann wird auch kein Callback aufgerufen
# Wer alles diese Information erhält, geht dem Temperatursensor nichts an
# Ausserdem ist in einem solchen Interface auch implementiert, dass bei Entfernung von Anzeigen
# auch die Registrierungen für deren Empfangsnachrichten wieder gelöscht werden
# und somit keine nicht mehr vorhandenen Callbackfunktionen aufgerufen werden.
# Das wäre etwa der Fall, wenn der User ein Toplevel Window schliesst
# und dadurch zerstört. Die verwendeten abgeleiteten Klassen für Gui Container
# nehmen in ihrer destroy Methode auch die Deregistrierung der für sie registrierten
# Empfangsnachrichten vor. Gleiche Empfangsnachrichten für andere Empfänger bleiben dabei unberührt
Sirius3
User
Beiträge: 17753
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: schön, Dein Message-Broker, wobei Zeile 2 und 7 nur verschiedene Varianten sind, wie man einen Aufruf schreiben könnte. Es fehlt noch vehicle.statusbus.aussentemperatur(23). Aber was hat das jetzt mit dem Thema des Threads „globale Variablen“ zu tun?
Antworten