Ich möchte eine Fadenpendel-Klasse schreiben

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
Körschgen
User
Beiträge: 5
Registriert: Samstag 15. September 2007, 10:55
Kontaktdaten:

Hallo,

das ist mein erster Beitrag im Forum. Ich bin auch in Python und OOP noch sehr neu. Vorhergehende Kenntnisse habe ich in PHP und C/C++.

Ich habe mit Borland C-Builder eine kleine Simulation eines Fadenpendels programmiert. Nun muss ich eine Projektarbeit über OOP schreiben und möchte daher eine Pendel-Klasse basteln. Dabei habe ich mich entschlossen gleich noch python zu lernen...

Die Pendel-Klasse soll folgendes können:
  • Angaben zu Pendellänge, Amplitude(bzw. max. Winkel) und Phasenwinkel aufnehmen können
  • daraus Frequenz, Schwingungsdauer, Kreisfrequenz berechnen
  • max. Geschwindigkeit, max. Beschleunigung berechnen
  • zu einem angegebenen Zeitpunkt Elongation, Winkel, Geschwindigkeit und Beschleunigung berechnen
das hab ich bis jetzt:

Code: Alles auswählen

import math
from math import sqrt
from math import sin

class Pendel(object):
    def __init__(self, laenge, amplitude, phasenwinkel):
        self.l          = laenge
        self.winkel_max = amplitude
        self.fi         = phasenwinkel
        self.y          = None
        self.y_max      = None
        self.f          = None
        self.T          = None
        self.omega      = None
        self.winkel     = None
        self.RAD_winkel = None
        self.v          = None
        self.v_max      = None
        self.a          = None
        self.a_max      = None

    def calc(self):
        self.T = 2*math.pi*sqrt(self.l/9.81)
        print "Die Schwingungsdauer beträgt ",self.T," sek."
        
        self.f = 1/self.T
        print "Die Frequenz beträgt ",self.f," Hz"
        
        self.omega = 2*math.pi*self.f
        print "Die Kreisfrequenz beträgt ",self.omega," 1/sek"

        self.y_max = sin(self.winkel_max*math.pi/180)

    def calc_v_max(self):
        self.v_max = self.y_max*2*math.pi*self.f
        print "Die max. Geschwindigkeit beträgt ",self.v_max," m/s"
Folgende Fragen habe ich dazu:
- ist der Code bis jetzt ok?
- Warum muss man jede einzelne Funktion aus math importieren
- wieso kommt eine Fehlermeldung, wenn ich eine Instanz der Klasse erstelle ohne die Attribute anzugeben und dann die Methode calc() aufrufe? z.B.

Code: Alles auswählen

>>> z = Pendel
>>> z.calc()

Traceback (most recent call last):
  File "<pyshell#37>", line 1, in <module>
    z.calc()
TypeError: unbound method calc() must be called with Pendel instance as first argument (got nothing instead)
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Code: Alles auswählen

z = Pendel(laenge, amplitude, phasenwinkel)
sollte funktionieren

Code: Alles auswählen

import math
reicht. Dann immer mit math.Funktionsname darauf zugreifen, z.B.: math.sqrt. Oder

Code: Alles auswählen

from math import pi, sqrt, sin
Dann reicht pi, sqrt(), sin() zum Aufrufen.
Ganz schlecht ist

Code: Alles auswählen

from math import *
MfG
HWK

Edit: Argumente bei Pendel() vergessen.
Zuletzt geändert von HWK am Samstag 15. September 2007, 11:50, insgesamt 2-mal geändert.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Der Code sieht eigentlich ganz OK aus, auch wenn ich die Berechnung noch von der Ausgabe der Ergebnisse trennen würde. Wenn das nur zu Demonstrationszwecken ist, würde ich diese einfache Variante beibehalten, da es den Code nicht aufbläht. Außerdem würde ich englische Bezeichner verwenden und den Attriuten aussagekräftigere Namen geben als "l", "y" oder "f".

Du musst auch nicht jede einzele Funktion aus math importieren. Wenn du zum Beispiel "sqrt" nutzen möchtest kannst du darauf qualifiziert zugreifen. So weiss man, woher der Name kommt:

Code: Alles auswählen

import math
print math.sqrt(9)
Zu deiner letzten Frage: mit "z = Pendel" erzeugst du keine neue Instanz der Klasse, sondern nur eine Referenz auf diese. Um eine Instanz zu erzeugen, musst du die Klasse aufrufen: "z = Pendel()".

Aber auch hier musst du natürlich Parameter angeben, denn beim Erzeugen die "__init__"-Methode aufgerufen wird, welche zweingend die Parameter "laenge", "amplitude" und "phasenwinkel" benötigt. Diese Informationen benötigst du ja auch später bei der Berechnung.
BlackJack

Der Style Guide empfiehlt Leerzeichen um binäre Operatoren und nach Kommata.

In der Physik werden in Formeln kurze Namen verwendet deren Bedeutung "allgemein" bekannt sind, aber in Programmen sind richtige, aussagekräftige Namen oft besser geeignet.

Warum bindest Du alle (Zwischen)ergebnisse an das Objekt? Die ganzen `None`-Zuweisungen würde ich weglassen und in den Methoden stattdessen lokale Namen verwenden.

Ausserdem könnte man die Methoden die Ergebnisse zurückgeben lassen und ausserhalb ausgeben. Dann lässt sich die Pendel-Klasse auch wiederverwenden, wenn man eine GUI "draufsetzen" möchte.

Das mit dem importieren ist halt so. Entweder Du importierst einen Namen, oder Du sprichst ihn über das Modul an. Man kann auch alle Namen in einem Modul auf einen Schlag importieren, aber davon ist dringend abzuraten. Bei mehreren solchen importen verliert man den Überblick welcher Name woher kommt, es kann zu Namenskollisionen kommen und wenn man das über mehrere "Ebenen" hinweg macht, ist das Konzept von Modulen ausgehebelt.
Körschgen
User
Beiträge: 5
Registriert: Samstag 15. September 2007, 10:55
Kontaktdaten:

Schonmal danke für eure Hilfe...
BlackJack hat geschrieben:Warum bindest Du alle (Zwischen)ergebnisse an das Objekt? Die ganzen `None`-Zuweisungen würde ich weglassen und in den Methoden stattdessen lokale Namen verwenden.

Ausserdem könnte man die Methoden die Ergebnisse zurückgeben lassen und ausserhalb ausgeben. Dann lässt sich die Pendel-Klasse auch wiederverwenden, wenn man eine GUI "draufsetzen" möchte.
Das verstehe ich nicht so ganz, kannst du das vllt. an einem Beispiel erklären?
Und was genau meinst du mit "lokale Namen" ... sorry, wenn die Frage dumm ist, aber ich weiß es einfach nicht :D

Code: Alles auswählen

import math

class pendulum(object):
    def __init__(self, length, amplitude, phase_angle):
        self.length     = length
        self.max_angle  = amplitude
        self.phase_angle= phase_angle

    def calc(self):
        duration = 2 * math.pi * math.sqrt(self.length / 9.81)
               
        frequency = 1 / duration
        
        ang_frequency = 2 * math.pi * frequency

        amplitude = math.sin(self.max_angle * math.pi / 180)

        return duration, frequency, ang_frequency

    def calc_max_speed(self):
        max_speed = amplitude * 2 * math.pi * frequency
        return max_speed
Etwa so?
BlackJack

Ja so meinte ich das. Der Unterschied zwischen lokalem Namen in Methoden oder Funktionen und Attribute auf dem Objekt besteht bei C++ auch. Lokale Namen existieren nur innerhalb eines Funktionsaufrufs.

Da es sich bei den Berechnungen um so etwas wie berechnete Attribute oder einfache Eigenschaften des Pendels handelt, könnte man auch Properties daraus machen. Siehe Doku zur `property()`-Funktion.
Körschgen
User
Beiträge: 5
Registriert: Samstag 15. September 2007, 10:55
Kontaktdaten:

Ich hab festgestellt, dass ich einige Größen, wie halt Frequenz und Schwingungsdauer lieber doch als Attribut speichern sollte, sonst kann ja z.B. die Funktion calc_max_speed (und andere) nicht darauf zugreifen ...

Und was ist jetzt der Unterschied, wenn ich die Ergebnisse nicht als Attribut sondern als properties speichern würde?
Ich kann zwar Englisch, aber durch die Doku seh ich nicht ganz so durch
Benutzeravatar
DatenMetzgerX
User
Beiträge: 398
Registriert: Freitag 28. April 2006, 06:28
Wohnort: Zürich Seebach (CH)

Körschgen hat geschrieben:Ich hab festgestellt, dass ich einige Größen, wie halt Frequenz und Schwingungsdauer lieber doch als Attribut speichern sollte, sonst kann ja z.B. die Funktion calc_max_speed (und andere) nicht darauf zugreifen ...

Und was ist jetzt der Unterschied, wenn ich die Ergebnisse nicht als Attribut sondern als properties speichern würde?
Ich kann zwar Englisch, aber durch die Doku seh ich nicht ganz so durch
Würde ich nicht machen... wenn die Frequenz oder so noch nie berechnet wurde, rechnest du nachher entweder mit Null oder 0 und da würden falsche werte rauskommen. Schreibe eine Funktion welche den wert zurück gibt, wenn du nun in der calc_max_speed methode die frequenz oder so brauchst rufst du die Funktion auf und speicherst den Return value in einer lokalen Variable...
BlackJack

@Körschgen: Properties werden berechnet wenn man darauf zugreift.

Code: Alles auswählen

In [147]: class Circle(object):
   .....:     def __init__(self, diameter):
   .....:         self.diameter = diameter
   .....:
   .....:     @property
   .....:     def radius(self):
   .....:         return self.diameter / 2
   .....:

In [148]: circle = Circle(10)

In [149]: circle.diameter
Out[149]: 10

In [150]: circle.radius
Out[150]: 5
Sowohl Durchmesser als auch Radius sehen bei der Benutzung der Klasse aus wie einfache Attribute, aber eines davon wird beim Zugriff berechnet. Wenn man nur Lesezugriff auf das berechnete Attribut haben möchte kann man `property()` wie in dem Beispiel als Dekorator mit dem ``@`` benutzen.
Körschgen
User
Beiträge: 5
Registriert: Samstag 15. September 2007, 10:55
Kontaktdaten:

Was meint ihr dazu :

Code: Alles auswählen

import math

class cpendulum(object):
    def __init__(self, length, amplitude, phase_angle):
        self.length     = length
        self.max_angle  = amplitude
        self.phase_angle= phase_angle

    def uration(self):
        duration   = 2 * math.pi * math.sqrt(self.length / 9.81)
        return duration
    
    def frequency(self):
        duration = self.duration()
        frequency  = 1 / duration
        return frequency

    def amplitude(self):        
        amplitude  = math.sin(self.max_angle * math.pi / 180)
        return amplitude

    def max_speed(self):
        amplitude = self.amplitude()
        frequency = self.frequency()
        max_speed = amplitude * 2 * math.pi * frequency
        return max_speed
    
    def max_acc(self):
        amplitude = self.amplitude()
        frequency = self.frequency()
        max_acc = -amplitude * math.pow(2 * math.pi * frequency, 2)
        return max_speed
Funktioniert bis jetzt sehr gut ...
BlackJack

Bei der Definition von `duration()` fehlt das 'd' und `max_acc()` sollte sicher nicht `max_speed` zurückgeben. Ich würde den letzten Namen in den ganzen Methoden sparen und gleich die Rechnung hinter die ``return``-Anweisung setzen.

Und der Klassenname sollte `Pendulum` lauten, damit man auch weiss, dass es eine Klasse ist.
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Was bei periodischen Bewegungen meistens eine wichtige Rolle spielt, ist die Kreisfrequenz, also `sqrt(g/l)`.

Für die Umrechnung in rad kannst du auch `math.radians()` verwenden.

In deinem amplitude() fehlt ein "* self.length".

Grundsätzlich würde ich für die Basisgrößen keine Klasse verwenden, sondern eine Hilfsfunktion, die alle Größen ausrechnet und als Dictionary zurückgibt:

Code: Alles auswählen

g = 9.81

def pendulum(L, phi_max):
    omega = math.sqrt(g / L)
    f = omega / (2 * math.pi)
    T = 1 / f
    ...
    return locals()
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Übrigens hat mich deine Formel für die maximale Geschwindigkeit gerade ziemlich ins Grübeln gebracht. Sowohl über Energieerhaltung als auch über die Differentialgleichung mit Kleinwinkelnäherung kommt man nämlich auf andere Werte.
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Körschgen
User
Beiträge: 5
Registriert: Samstag 15. September 2007, 10:55
Kontaktdaten:

@birkenfeld: Das mit der Geschwindigkeit stimmt, das ist ein Fehler drin ...

Und wie meinst du das mit "für die Basisgrößen keine Klasse verwenden" ?

edit:
Hab mal einige der genannten Fehler behoben:

Code: Alles auswählen

import math

class Pendulum(object):
    def __init__(self, length, max_angle, phase_angle):
        self.length     = length
        self.max_angle  = max_angle
        self.phase_angle= phase_angle

    def duration(self):
        return 2 * math.pi * math.sqrt(self.length / 9.81)
   
    def frequency(self):
        duration = self.duration()
        return 1 / duration

    def amplitude(self):       
        return math.sin(math.radians(self.max_angle)) * self.length

    @property            
    def max_speed(self):
        height = math.cos(self.max_angle) * self.length
        return math.sqrt(2 * 9.81 * height) 
    
    @property
    def max_acc(self):
        amplitude = self.amplitude()
        return -amplitude * 9.81 / self.length

Antworten