Klasse und Funktion

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
T-Bird92
User
Beiträge: 8
Registriert: Freitag 28. Oktober 2016, 08:53

Code: Alles auswählen

class PID_Controller:
    def __init__(self):
        self.Kp = 0.0
        self.Ki = 0.0
        self.Kd = 0.0
        self.Setpoint = 0.0
        self.Fehler = 0.0
        self.PID_Val = 0.0
        self.Val_Max = 0.0
        self.Val_Min = 0.0
        self.P_Anteil = 0.0
        self.I_Anteil = 0.0
        self.D_Anteil = 0.0
        self.Integrator = 0.0
        self.Differenzierer = 0.0
        self.Integrator_Max = 100.0
        self.Integrator_Min = -100.0

    def Set_Parameters(self, P, I, D, Setpoint, PID_Max, PID_Min):
        self.Kp = P
        self.Ki = I
        self.Kd = D
        self.Setpoint = Setpoint
        self.Val_Max = PID_Max
        self.Val_Min = PID_Min

    def Compute(self, Meassurement):
        self.Fehler = self.Setpoint - Meassurement

        self.P_Anteil = self.Kp * self.Fehler

        self.D_Anteil = self.Kd * (self.Fehler - self.Differenzierer)
        self.Differenzierer = self.Fehler

        self.Integrator = self.Integrator + self.Fehler
        if self.Integrator > self.Integrator_Max:
            self.Integrator = self.Integrator_Max
        elif self.Integrator < self.Integrator_Min:
            self.Integrator = self.Integrator_Min
        self.I_Anteil = self.Ki * self.Integrator

        self.PID_Val = self.P_Anteil + self.I_Anteil + self.D_Anteil
        if self.PID_Val > self.Val_Max:
            self.PID_Val = self.Val_Max
        elif self.PID_Val < self.Val_Min:
            self.PID_Val = self.Val_Min
        return self.PID_Val

P = 5.88
I = 0.08647
D = 99.96

Sollwert = 110.0

DC_Max = 10.0
DC_Min = 4.0

Messwert = 0.0

PID_Controller.Set_Parameters(P=P, I=I, D=D, Setpoint=Sollwert, PID_Max=DC_Max, PID_Min=DC_Min)
DutyCycle = PID.Compute(Messwert)
Hi Leute,

Habe mir einen PID-Regler geschrieben.
Die Funktion Set_Parameter stellt den Regler ein.
Die Funktion Compute Soll den Reglerwert berechnen.

Nun ist es aber so, dass meine Entwicklungsumgebung mir beim Aufruf der Funktionen Fehler gibt.

PID_Controller.Set_Parameters(P=P, I=I, D=D, Setpoint=Sollwert, PID_Max=DC_Max, PID_Min=DC_Min):
"This inspection reports discrepancies between declared parameters and actual arguments, as well as incorrect arguments (e.g. duplicate named arguments) and incorrect argument order. Decorators are analyzed, too."

DutyCycle = PID.Compute(Messwert):
"Expected type 'PID_Controller', got 'float' instead less... (⌘F1)
This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings or in Python 3 function annotations."

Könnt ihr mir sagen woran das liegt?
Bin der Meinung es passt so.
Zuletzt geändert von Anonymous am Dienstag 1. November 2016, 14:03, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Du rufst Methoden auf der Klasse statt der Instanz auf. Das führt zu einem Fehler.
BlackJack

@T-Bird92: Du hast beim Aufruf das erste Argument vergessen: `self`. Aber *eigentlich* möchtest Du nicht die ungebundene Methode auf der Klasse aufrufen, sondern ein Exemplar von `PID_Controller` erstellen und *darauf* dann die gebundene Methode aufrufen.

Beim zweiten Fehler stimmt der gezeigte Quelltext nicht mit dem Fehler überein. Im Quelltext definierst Du nirgends `PID`, also sollte da ein `NameError` kommen. Die Fehlermeldung sieht so aus als hättest Du in Deinem Quelltext irgendwo ``PID = PID_Controller`` stehen. Du möchtest dort `PID_Controller` aber *aufrufen* und das dadurch neu erstellte `PID_Controller`-Exemplar an den Namen `PID` binden.

Warum ist das eine Klasse mit drei Methoden statt einer Funktion? Und müssen da wirklich all diese Attribute an die Klasse gebunden werden? Sind da nicht welche einfach nur lokale Namen für die Berechnung? Nach der `__init__()` sollte ein Objekt normalerweise in einem benutzbaren Zustand sein. Kann man `Compute()` aufrufen ohne vorher `Set_Parameters()` aufgerufen zu haben? Ist es sinnvoll `Set_Parameters()` aufzurufen nachdem `Compute()` ausgeführt wurde? Man übernimmt dann ja Zustand von vorherigen `Compute()` -Aufrufen die durch `Set_Parameters()` nicht beeinflusst werden, aber darauf folgende `Compute()`-Aufrufe beeinflussen. Das sieht alles ein bisschen fragil aus.
T-Bird92
User
Beiträge: 8
Registriert: Freitag 28. Oktober 2016, 08:53

@T-Bird92: Du hast beim Aufruf das erste Argument vergessen: `self`. Aber *eigentlich* möchtest Du nicht die ungebundene Methode auf der Klasse aufrufen, sondern ein Exemplar von `PID_Controller` erstellen und *darauf* dann die gebundene Methode aufrufen.

Beim zweiten Fehler stimmt der gezeigte Quelltext nicht mit dem Fehler überein. Im Quelltext definierst Du nirgends `PID`, also sollte da ein `NameError` kommen. Die Fehlermeldung sieht so aus als hättest Du in Deinem Quelltext irgendwo ``PID = PID_Controller`` stehen. Du möchtest dort `PID_Controller` aber *aufrufen* und das dadurch neu erstellte `PID_Controller`-Exemplar an den Namen `PID` binden.

Warum ist das eine Klasse mit drei Methoden statt einer Funktion? Und müssen da wirklich all diese Attribute an die Klasse gebunden werden? Sind da nicht welche einfach nur lokale Namen für die Berechnung? Nach der `__init__()` sollte ein Objekt normalerweise in einem benutzbaren Zustand sein. Kann man `Compute()` aufrufen ohne vorher `Set_Parameters()` aufgerufen zu haben? Ist es sinnvoll `Set_Parameters()` aufzurufen nachdem `Compute()` ausgeführt wurde? Man übernimmt dann ja Zustand von vorherigen `Compute()` -Aufrufen die durch `Set_Parameters()` nicht beeinflusst werden, aber darauf folgende `Compute()`-Aufrufe beeinflussen. Das sieht alles ein bisschen fragil aus.
Ja kann man natürlich auch einfach eine Funktion schreiben. Ich möchte aber lernen, wie das mit den Klassen funktioniert.

Muss ich das so machen?

Code: Alles auswählen

PID = PID_Controller
PID.Set_Parameters(P,I,D,Sollwert,DC_Max,DC_Min)
DutyCycle = PID.Compute(Messwert)
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

T-Bird92 hat geschrieben:

Code: Alles auswählen

PID = PID_Controller
Damit bindest du die Klasse PID_Controller nur an einen weiteren Namen. Schau noch mal ins Tutorial: Class Objects
BlackJack

@T-Bird92: Wenn man stattdessen eine Funktion schreiben kann, dann hast Du aber nicht gelernt wie man Klassen schreibt, denn in so einem fall würde man ja keine Klasse schreiben. Auch das ist wichtig zu lernen: Wann Klassen überhaupt Sinn machen. Die sind kein Selbstzweck. Man kann an einem Beispiel das keinen Sinn macht, höchstens die Syntax lernen, aber nicht wie man eine *richtige* Klasse schreibt. Zum Beispiel das Du hier scheinbar einfach *alles* an das Objekt bindest ist falsch. Das macht keinen Sinn und das würde man so nicht schreiben.

Wenn man das mal sein lässt, dann sieht man was das Objekt *tatsächlich* für einen Zustand hat, weil die nicht mehr in den ganzen Attributen untergehen die *keinen* Zustand darstellen:

Code: Alles auswählen

class PIDController(object):

    INTEGRATOR_MAX = 100
    INTEGRATOR_MIN = -100

    def __init__(self):
        self.k_p = None
        self.k_i = None
        self.k_d = None
        self.setpoint = None
        self.val_max = None
        self.val_min = None

        self.differenzierer = 0
        self.integrator = 0

    def set_parameters(self, p, i, d, setpoint, pid_max, pid_min):
        self.k_p = p
        self.k_i = i
        self.k_d = d
        self.setpoint = setpoint
        self.val_max = pid_max
        self.val_min = pid_min

    def compute(self, meassurement):
        fehler = self.setpoint - meassurement
        p_anteil = self.k_p * fehler
        d_anteil = self.k_d * (fehler - self.differenzierer)
        self.differenzierer = fehler
        self.integrator = max(
            min(self.integrator + fehler, self.INTEGRATOR_MAX),
            self.INTEGRATOR_MIN
        )
        i_anteil = self.k_i * self.integrator
        return max(
            min(p_anteil + i_anteil + d_anteil, self.val_max), self.val_min
        )
Hier stellt sich nun die Frage warum `set_parameters()`, wenn die auf jeden Fall aufgerufen werden muss, damit das Objekt in einem benutzbaren Zustand ist. Wenn man die beseitigt, dann hat man nur noch die `__init__()` und eine weitere Methode. Meistens ein Zeichen, dass man eine Funktion daraus machen kann und dann auch sollte. Hier aber eine Funktion mit innerem Zustand. Also ist die Klasse okay, aber bei nur einer Methode könnte man die in `__call__()` umbenennen und das Objekt damit aufrufbar machen.

Code: Alles auswählen

class PIDController(object):

    INTEGRATOR_MAX = 100
    INTEGRATOR_MIN = -100

    def __init__(self, p, i, d, setpoint, pid_max, pid_min):
        self.k_p = p
        self.k_i = i
        self.k_d = d
        self.setpoint = setpoint
        self.val_max = pid_max
        self.val_min = pid_min

        self.differenzierer = 0
        self.integrator = 0

    def __call__(self, measurement):
        fehler = self.setpoint - measurement
        p_anteil = self.k_p * fehler
        d_anteil = self.k_d * (fehler - self.differenzierer)
        self.differenzierer = fehler
        self.integrator = max(
            min(self.integrator + fehler, self.INTEGRATOR_MAX),
            self.INTEGRATOR_MIN
        )
        i_anteil = self.k_i * self.integrator
        return max(
            min(p_anteil + i_anteil + d_anteil, self.val_max), self.val_min
        )
Aufruf:

Code: Alles auswählen

    # ...
    control = PIDController(p, i, d, sollwert, dc_max, dc_min)
    for messwert in messwerte:
        duty_cycle = control(messwert)
        # ...
Falls das Problem als Konvertierung einer Folge von Messwerten darstellbar ist, würde ich allerdings eine Generatorfunktion statt einer Klasse schreiben.
BlackJack

Noch eine kleine Überarbeitung um zu zeigen, dass man für unveränderlichen Zustand keine Klasse braucht:

Code: Alles auswählen

from functools import partial


def limit(min_value, max_value, value):
    return max(min(value, max_value), min_value)


class PIDController(object):

    LIMIT_INTEGRATOR = partial(limit, -100, 100)

    def __init__(self, factors, setpoint, result_limits):
        self.k_p, self.k_i, self.k_d = factors
        self.setpoint = setpoint
        self.limit_result = partial(limit, *result_limits)

        self.differenzierer = 0
        self.integrator = 0

    def __call__(self, measurement):
        fehler = self.setpoint - measurement
        p_anteil = self.k_p * fehler
        d_anteil = self.k_d * (fehler - self.differenzierer)
        self.differenzierer = fehler
        self.integrator = self.LIMIT_INTEGRATOR(self.integrator + fehler)
        i_anteil = self.k_i * self.integrator
        return self.limit_result(p_anteil + i_anteil + d_anteil)
Aufruf:

Code: Alles auswählen

# ...
    control = PIDController((p, i, d), sollwert, (dc_min, dc_max))
    for messwert in messwerte:
        duty_cycle = control(messwert)
        # ...
Benutzeravatar
noisefloor
User
Beiträge: 3853
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@T-Bird92: warum postest du eigentlich exakt die gleichen Python Fragen in zwei Foren (http://www.forum-raspberrypi.de/Thread- ... ner-klasse)? Gürtel-Hosenträger Mentalität? Traust du einem Forum alleine nicht?

Wenn du wenigstens das Gelernte aus Forum A nach Forum B übertragen würdest, dann hätten alle was davon. Tust du aber nicht. Da müsstest du vielleicht an deiner Einstellung, der Community was zurück zu geben, noch ein bisschen arbeiten...

Gruß, noisefloor
Antworten