Klasse als Datenspeicher, die nur einmal existiert

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
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Hallo,

ich möchte Informationen in einer Klasse speichern, da ich keine globale Variable benutzen möchte. Der Inhalt soll einfach erstmal ein Integer sein und zwei Methoden haben:
put und get.

Code: Alles auswählen

class Datenspeicher:
    def __init__(self):
        self._value=0   # Initialwert
        
    def get(self):
        return(self._value)
    def put(self, value):
        self._value=value
        
speicher1 = Datenspeicher()
speicher2 = Datenspeicher()
speicher1 und speicher2 sollen aber nicht zwei unterschiedliche Datenspeicher sein, sondern die gleichen. Ich habe im Internet gelesen, dass ich dann die Klasse so zurechtmachen muss, dass es sie nur einmal gibt - Singleton. Dazu habe ich unterschiedliche Beispiele gefunden, die ich aber noch nicht verstanden habe. Hier auf dem Link habe ich einige Beispiele gefunden:

http://code.activestate.com/recipes/52558/

Zunächst einmal kommt es vor, dass in der Klasse Variablen stehen, die nicht in einer Funktion stehen, z.B. so:

Code: Alles auswählen

class Example():
    __instance=None
    
    def __init__(self):
        ...
    def __call__(self):
        ...

Da __instance nicht in einer Funktion steht, ist __instance in diesem Fall eine gloabe Variable innerhalb der Klasse Example?

Es gibt einige Beispiele in dem Link mit einer Funktion __call__(self): ... und nicht immer ist eine Funktion __init__(self): ... vorhanden. Muss man nicht zwangsläufig eine Methode __init__(self): ... in einer Klasse haben, die irgendwas macht beim Aufrufen? Alles, was mit zwei Unterstrichen anfängt wie __init... oder nun auch das __call__.. hat ja irgendwie eine besondere Bedeutung. __init__ wird aufgerufen, wenn das Objekt erzeugt wird mit var = Example(), dann wird. Das habe ich nun verstanden. Wann wird denn die Funktion __call__ ausgeführt?

In einigen der Beispiele gibt es den Fall, dass in der Klasse eine weitere Klasse erzeugt wird:

Code: Alles auswählen

class Singleton:

    class __impl:
          ....
Ist __impl auch eine spezielle Funktion wie z.B. __init__ oder ist es ein selbstgewählter Name?

Wenn man eine Singleton-Klasse erzeugt und man zwei Threads hat, die Daten von dort haben wollen oder hineinschaufeln, muss man dann ggf. auch darauf achten, dass man den Zugriff für Prozess 2 solange sperrt, bis Prozess 1 fertig ist - oder macht das der Interpreter von selber mit der Zugriffssteuerung, weil man von selber dafür sorgt, dass man ein Singleton-Pattern gewählt hat?
BlackJack

Wenn Du das nicht als globale Variable haben möchtest, dann kannst Du das auch nicht in ein Singleton stecken, denn das ist doch auch eine globale Variable! Ansonsten kann man technisch Magie betreiben um ein Singleton zu erstellen, das würde ich aber nicht machen. Entweder nimmt man ein Modul, denn das sind in Python Singletons, oder eine normale Klasse von der man halt einfach nur ein Exemplar erstellt.
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

BlackJack hat geschrieben:... Entweder nimmt man ein Modul, denn das sind in Python Singletons, oder eine normale Klasse von der man halt einfach nur ein Exemplar erstellt.
Was meinst Du denn mit Modul? Unter Modul verstehe ich jetzt, dass man eine Datei erstellt, z.B. modul1.py und diese Datei mit import in einem anderen Programm einbindet. Ich hab auch nochmal nach Modul python gegooglet und da kam das heraus.

Wenn ich z.B. jetzt in einer Datei schreibe:

modul.py:
datenspeicher=1.0

und in einer anderen Datei:

main.py:
import modul

modul.datenspeicher=10

meinst Du das so?
BlackJack

@Papp Nase: Ja, so war das gemeint. Das Modulobjekt ist immer das selbe, egal in welchem anderen Modul man das importiert.
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Was soll das ganze Vorgehen überhaupt für einen Sinn haben? Wenn man eine Klasse benutzt, die allein dazu dient, einen Wert mit Getter- und Setter-Methoden zu umhüllen und dann ein Exemplar dieser Klasse global ablegt, dann hat man ja letztlich auch wieder eine globale Variable erzeugt - nur über Umwege. Was hat einem das dann gebracht?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Genau, welches Problem soll denn überhaupt gelöst werden?

Geht es um "Programm Einstellungen" ?

Im einfachsten Fall, gehe ich dann hin und mache ein paar "Konstanten":

Code: Alles auswählen

EINSTELLUNG_A = 12
EINSTELLUNG_B = "foo"

def mein_programm():
    for x in xrange(EINSTELLUNG_A):
        ...
Oder aber eine "Config-Klasse":

Code: Alles auswählen

class Config(object):
    EINSTELLUNG_A = 12
    EINSTELLUNG_B = "foo"

def mein_programm(cfg):
    for x in xrange(cfg.EINSTELLUNG_A):
        ...

if __name__ == "__main__":
    cfg=Config()
    mein_programm(cfg)
Gibt noch zig Variationen die man so machen könnte... z.B. das Config() eine .ini Datei mit Einstellungen liest. Dann auf modulebene eine Instanz davon machen und die überall benutzten oder halt "rumreichen"...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

snafu hat geschrieben:Was hat einem das dann gebracht?
Ich habe mal in einer anderen Programmiersprache programmiert. Dort konnte man zum Hin- und Herschaufeln von Daten eine globale Variable erstellen und dann bei den Variableneigenschaften sagen, dass die Variable vom Typ Singleton sein soll. Und wenn ich das angeklickt habe, dann gab es keine Laufzeitprobleme beim Zugriff auf die Variable. Wenn zwei Prozesse gleichzeitig auf die identische Ressource zugreifen wollten, dann hat der Compiler selber dafür gesorgt, dass der Prozess, der zuerst da war, drangekommen ist und der zweite Prozess solange gewartet hat, bis die Operation bei Prozess abgeschlossen war.

Bis jetzt war es in Python so - wenn ich nur eine Variable (egal ob Array, Liste oder auch nur ein Einzelwert), dann musste ich ihn an der Wurzel definieren z.B. in der Main-Methode und mittels Argumente an alle Unterfunktionen durchreichen. Und so kam mir die Idee, das mit dem Singleton in Python auch hinzukriegen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Und zwischen was willst du Variablen austauschen? Prozesse erzeugt mit dem multiprocess Modul oder Threads?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@Papp Nase: Du willst also gar kein Singleton sondern ein Objekt bei dem der Zugriff threadsicher ist. Das ist im allgemeinen *keine* Eigenschaft von Singletons. Das dazu passende Fachwort ist da eher „Monitor”. *So* etwas könnte man sich in Python natürlich auch basteln, allerdings muss man da aufpassen, dass das natürlich nur den direkten Zugriff auf das Objekt schützt. Wenn sich dann zwei Threads über dieses Objekt die selbe Liste, oder irgend ein anderes veränderbares Objekt abholen, können die nach dem Zugriff natürlich damit unsichere Sachen anstellen.

Statt Prozesse meinst Du sicher Threads, oder? Bei Prozessen ist ja eigentlich gegeben das normalerweise gar keinen gemeinsamen Speicher haben.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ob nun Threads oder Prozesse: Generell verlangsamt sowas den Ablaufe, weil halt irgendwie synchronisiert werden muß.
Keine Ahnung ob das auch zutrifft, wenn 99% gelesen wird und nur 1% geändert.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Ich habe mir hier ein Beispiel ausgedacht, was mein Problem verdeutlichen soll. Im Netz hab ich ein Beispiel für einen Ringspeicher gefunden. Der Ringspeicher hat zwei Funktionen: .add(value) zum anfügen und .array(), um den Inhalt auszugeben. Mit .add will ich - wenn es später mal klappt - in einem Thread Daten einfügen in den Ringspeicher. Die Inhalte des Ringspeichers sollen in einem Graphen angezeigt werden und nach einer festgesetzten Zeit auch aktualisiert werden. Ich habe Angst, dass es bei dem Reinschreibeprozess bei der add-Methode und dem Lesen mit der array-Methode zu Problemen kommen könnte, da sie ja in verschiedenen Threads ablaufen.

Ich habe bei diesem Beispiel allerdings noch ein Problem - ich weis nicht, wie ich einen Plot aktualisieren soll. Ich habe - um den Plot in ein tkinter-Frame einzubauen, mit Funktionen aus matplotlib.figure gearbeitet, nicht mit matplotlib.pyplot. Pyplot hat nicht funktioniert. Bei Pyplot gibt es die Methoden pyplot.ion(), um den interaktiven Modus einzuschalten, dann erreichte ich es in einem anderen Beispiel, dass bei Veränderung des Datenarrays auch der Plot aktualisiert wurde. Jetzt möchte ich die Daten erstmal per Tastendruck aktualisieren mit der Methode refresh_plot. Wenn das klappt, dann kann ich später - wie in diesem Beispiel: http://www.python-forum.de/viewtopic.ph ... 05&start=0 mit der after-Methode die Refresh-Funktion erneut aufrufen.

In einem anderen Beispiel habe ich zur Aktualisierung auch etwas mit funcanimation gefunden: http://jakevdp.github.io/blog/2012/08/1 ... -tutorial/

Aber dieses Beispiel arbeitet auch mit der Pyplot-Klasse und nicht mit der figure-Klasse. Ich habe es aber nicht hinbekommen, mit der Pyplot-Klasse die Einbindung in einen tkinter-Frame hinzukriegen :-(

Code: Alles auswählen

import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
import matplotlib.animation as animation

from matplotlib import pyplot as plt
import random

import time

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

class FIFOBuffer(object):
    """
    A circular FIFO buffer implemented on top of numpy.
    """
    __instance=None
    
    def __init__(self, shape, dtype=np.float32, filled=False):
        self._cache = np.zeros(shape, dtype)
        self._values = np.zeros(shape, dtype)
        self.shape = shape
        self._cached=True
        if filled:
            self._ind = self.shape[0]
        else:
            self._ind = 0
            self._cached = False
    
    def add(self, value):
        """
        Add a value to the buffer.
        """
        ind = self._ind % self.shape[0]
        self._values[ind] = value
        self._ind += 1
        self._cached = False
        
    def array(self):
        """
        Returns a numpy array containing the last stored values.
        """
        if self._ind < self.shape[0]:
            return self._values[:self._ind]
        if not self._cached:
            ind = self._ind % self.shape[0]
            self._cache[:self.shape[0] - ind] = self._values[ind:]
            self._cache[self.shape[0] - ind:] = self._values[:ind]
            self._cached = True
        return self._cache
    
    def __len__(self):
        return min(self._ind, self.shape[0])

def create_data(channels, container):
    for i in range (0, channels):
        container[i].add(random.random()*10)

def print_data(channels, container):
    for i in range (0, channels):
        print (container[i].array())
        
class PlotDisplay(tk.Frame):
    def __init__(self, master, channels, data, style_args, refreshtime=100):
        # tk.Frame.__self__(self, master=master)
        self._master = master
        self._data = data
        self._styleargs = style_args
        self._refreshtime = refreshtime
        self._channels = channels
        self._fig = Figure()
        #self._fig = plt.Figure()
        #plt.ion()
        self._plot1 = self._fig.add_subplot(111)
                    
        self._canvas = FigureCanvasTkAgg(self._fig, master=self._master)
        self._canvas.show()
        self._canvas._tkcanvas.pack()
        print ("in plotdisplayinit")
        
        self._init_plot()
        
    def _init_plot(self):
        print ("in initplot")
        for i in range (0, self._channels):
            print (self._data[i].array())
            # plt.plot(self._data[i].array(), self._styleargs[i])
            self._plot1.plot(self._data[i].array(), self._styleargs[i])

    def refresh_plot(self):
        print ("in refresh_plot")
        create_data(self._channels, self._data)
        print_data (self._channels, self._data)

def main ():

    root = tk.Tk()    
    
    def do_exit():
        root.quit()
        root.destroy()
        
      
    plotframe = tk.Frame(master=root)
    buttonframe = tk.Frame(master=root)
    
    exit_button = tk.Button (buttonframe, text=" Beenden ", width=21, command=do_exit)
    exit_button.pack()
    
    refresh_button = tk.Button (buttonframe, text = " Refresh display ", width=21)
    refresh_button.pack()
    
    buttonframe.grid(row=0, column=2)
    plotframe.grid(row=0, column=1)
    
    style_args=['g-x', 'b*-', 'g','y--','r','b']
    channels=6
    values = 10
    maxdata=channels*values
    print ("Maxdata ---> %s" %str(maxdata))


    # init data_container
    data_container=[]
    for i in range (0,channels):
        data_container.append( FIFOBuffer(shape=[(values)], dtype=np.float16, filled=True) )
        

    create_data(channels, data_container)
    create_data(channels, data_container)
    create_data(channels, data_container)
    create_data(channels, data_container)
        
    display = PlotDisplay (plotframe, channels, data_container, style_args)
    refresh_button['command']=display.refresh_plot
    
    
    """  
    fig = plt.Figure()
    for i in range (0, channels):
        plt.plot(data_container[i].array(), style_args[i])
    plt.show() 
    """
    
    root.mainloop()
    
    print ("... PRG_end ...")
    

if __name__ == "__main__":
    main ()
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Papp Nase: Dein Ringspeicher ist kein Singleton, wäre ja auch blöd wenn's einer wäre, und braucht auch keinen besonderen Zugriffsschutz, wenn es nur einen Schreibthread gibt und Du das aktuell zu schreibende Element aus dem Lesezugriff herausnimmst. Also welches Problem soll damit verdeutlicht werden?
Benutzeravatar
snafu
User
Beiträge: 6908
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Generell lassen sich konkurrierende Zugriffe so schützen:

Code: Alles auswählen

import threading

class ThreadsafeData(object):
    def __init__(self, initial_value=None):
        self._value = initial_value
        self._lock = threading.Lock()

    def get(self):
        with self._lock:
            return self._value

    def set(self, value):
        with self._lock:
            self._value = value
Wenn ein "Lock-Block" noch aktiv ist und gleichzeitig ein weiterer Zugriff auf einen mit diesem Lock geschützten Codeabschnitt erfolgen soll - hier also: erneuter Aufruf von ``.get()`` oder ``set()`` aus einem anderen Thread heraus -, dann blockiert die Ausführung für Thread 2 solange bis Thread 1 den Block verlassen hat, denn erst dann wird das Lock wieder freigegeben. In meinem Beispiel ist das recht sinnfrei, aber es geht ja um's Prinzip.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Soll der "Ringspeicher" ein https://docs.python.org/2/library/queue.html sein?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@jens: nein, er soll auf numpy aufbauen, um effizient große Mengen an Zahlen zu verwalten.
Antworten