Objektorientierte Programmierung: Methode wird nicht erkannt

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
Cortez
User
Beiträge: 115
Registriert: Montag 31. Dezember 2018, 15:28

Hallo,

ich habe folgenden Code geschrieben:

Code: Alles auswählen

class Test:
    Array1=[]
    Array2=[1]
    def Funk1(self, Array1):
        i=0
        global summe1
        summe1=0
        while i <= (len(Array1)-1):
            summe1=summe1 + Array1[i]
            i=i+1
        global Durchschnittfunk1
        Durchschnittfunk1=summe1/len(Array1)
        
        
    def Funk2(self, Array2):
        i=0
        global summe2
        summe2=0
        while i<= (len(Array2)-1):
            summe2=summe2+Array2[i]
            i=i+1
        global Durchschnittfunk2
        Durchschnittfunk2=summe2/len(Array2)
        
        
    def Rechnung(self, Array1, Array2):
        self.Funk1(Array1)
        self.Funk2(Array2)
        
        global summe3

        summe3= summe1 + summe2
        
    def Arraybefüllen(self):
        i=1
        Anzahl=input("Wieviel Zahlen")
        while i <= int(Anzahl):
            Wert= input("Bitte Zahl eingeben")
            self.Array1.append(int(Wert))
            i=i+1
        print (self.Array1)
        
        
        def Array2befüllen(self):
            i=1
            Anzahl=input("Wieviel Noten")
            while i <= int(Anzahl):
                Wert= input("Bitte Zahl eingeben")
                self.Array2.append(int(Wert))
                i=i+1
            print (self.Array2)
    
Instanz=Test()



Instanz.Arraybefüllen()
Instanz.Array2befüllen()

Instanz.Rechnung(Instanz.Array1, Instanz.Array2)

print ("Array1 enthält die Werte " + str(Instanz.Array1))
print ("Der Durchschnitt von Array1 beträgt: " + str(Durchschnittfunk1))

print ("Array2 enthält die Werte " + str(Instanz.Array2))
print ("Der Durchschnitt von Array2 beträgt " + str(Durchschnittfunk2))

    
Leider erhalte ich folgende Fehlermeldung:

Code: Alles auswählen

Traceback (most recent call last):
  File "./test10.py", line 60, in <module>
    Instanz.Array2befüllen()
AttributeError: 'Test' object has no attribute 'Array2befüllen'
Dabei ist die Funktion doch im Prinzip genauso wie die Funktion 'Arraybefüllen', die tadellos funktioniert.
Weiß jemand, warum die Array2befüllen Probleme macht?
Benutzeravatar
sparrow
User
Beiträge: 4537
Registriert: Freitag 17. April 2009, 10:28

Die Einrückung ist bei Python schon wichtig.
Die unterscheidet sich bei den beiden Funktionen ja deutlich.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Einrückungen sind in Python wichtig. Wenn du deine Methode einfach eine Ebene tiefer einrückst, ist die eben nicht mehr auf Klassen-Ebene, sondern als lokale Funktion in Arraybefüllen definiert.

Und jenseits davon ist durch die konsequente Nutzung von globalen Zustand die Klasse eh wertlos. Berechnete Werte sollten Funktionen oder Methoden als Rückgabewerte verlassen, und Argument als Parameter in die die Funktionen hereingegeben werden. Die ganzen Funktionen und Bezeichner durchzunummerieren ist hochgradig verwirrend. Array1 sind Array2 sind KLASSENATTRIBUTE. Heißt: globale Variablen, die alle Test-Instanzen teilen.
Cortez
User
Beiträge: 115
Registriert: Montag 31. Dezember 2018, 15:28

Ok, danke für die rasche Antwort. Das mit dem Einrücken war ein Anfängerfehler - naja ich habe auch erst vor 2 Tagen mit Python angefangen.

Dass es nicht optimal ist, die Ergebnisse der Funktion als global zu deklarieren, habe ich mir schon fast gedacht. Aber wie wäre eine sinnvolle Alternative?
Benutzeravatar
__blackjack__
User
Beiträge: 14036
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Cortez: Das hat __deets__ doch schon geschrieben: Als Rückgabewert(e) an den Aufrufer zurückgeben. Wenn man Funktionen noch nicht drauf hat, sollte man nicht mit Klassen anfangen. Im Grunde sollte man vielleicht nicht einmal mit Funktionen anfangen wenn man Schleifen noch nicht drauf hat. Keine der ``while``-Schleifen sollte eine solche sein – das sollten alles ``for``-Schleifen sein. Und wo es geht direkt über die Elemente, ohne den unnötigen Umweg über einen Indexwert.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Benutzeravatar
__blackjack__
User
Beiträge: 14036
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Cortez: Noch ein paar Anmerkungen zum Quelltext: Die Klasse macht hier absolut keinen Sinn. Das sieht man im Grunde schon am Namen. Exemplare dieser Klasse stehen nicht für einen Test. Und es fällt mir auch kein passender Name für die Klasse ein. Dir etwa?

Namen schreibt man in Python klein_mit_unterstrichen, ausgenommen Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Um das ``=`` bei Zuweisungen ausserhalb von Argumentlisten schreibt man üblicherweise Leerzeichen. Zwischen Funktionsnamen und öffnender Klammer dagegen kein Leerzeichen. Das machst Du bei `print()` überall.

`array_irgendwas` ist ein falscher Name für eine *Liste*. Listen sind keine Arrays. Es gibt in Python auch Arrays, darum kann so etwas für Verwirrung sorgen. Aber auch `list` oder `liste` sollte in Namen nicht auftauchen. Den Leser interessiert doch weniger welcher Grunddatentyp hinter dem Namen steht, als vielmehr was der Wert im Kontext des Programms *bedeutet*. Um das Programm leichter zu verstehen ist es doch wichtiger zu wissen das in der einen Liste irgendwelche Zahlen und in der anderen Noten stehen, statt das das eine die erste Liste im Code ist und die andere die Zweite. Sequenzdatentypen bennent man üblicherweise in der Mehrzahl des Namens, den man einem einzelnen Element geben würde. Also hier beispielsweise `zahlen` und `noten`.

`Funk1()` und `Funk2()` sind ganz offensichtlich keine Methoden, weil die überhaupt nicht auf das Objekt zugreifen auf dem sie sitzen. Wenn man die aus der Klasse heraus holt, sieht man auch das `Rechnung()` ebenfalls keine Methode ist. Und mit `summe3` wird nirgends etwas gemacht. Irgendwelche Zahlen mit Noten aufzuaddieren macht ja auch gar keinen Sinn. Insgesamt macht `Berechne()` keinen Sinn. Das berechnet den Durchschnitt von irgendwelchen Zahle und den von Noten. Was hat das miteinander zu tun? Was wäre denn ein passender, nicht-generischer Name für diese Funktion?

Wenn man das ``global`` in `Funk1()` und `Funk2()` durch Rückgabewerte ersetzt, sieht man, dass beide Funktionen exakt das gleiche machen. Das ist also in Wirklichkeit nur eine einzige Funktion. Der man auch einen deutlich besseren Namen als `Funk_irgendwas` geben kann. Funktionen werden üblicherweise nach der Tätigkeit benannt, die sie durchführen. Also beispielsweise `berechne_durchschnitt()` in diesem Fall.

Zudem lässt sich die Funktion mit der eingebauten `sum()`-Funktion drastisch verkürzen.

`Arraybefüllen()` und `Array2befüllen()` heissen nicht nur verdammt ähnlich, sondern sehen auch extrem ähnlich aus. Wenn man die die Liste einfach selbst erzeugen und zurückgeben lässt, dann sind das auch ganz normale Funktionen die aus der ”Klasse” raus müssen, und wenn man den einzigen Unterschied durch ein Argument in diesen Code hinein bringt, dann ist das auch nur *eine* Funktion.

`Anzahl` sollte nicht an eine Zeichenkette mit Ziffern, sondern an eine Zahl gebunden werden.

Nun hat die ”Klasse” keine Methoden mehr. Nur noch die beiden globalen Listen die nicht wirklich etwas miteinander zu tun haben. Also weg mit der Klasse.

Da bleiben dann drei Funktionen übrig. Eine um den Durchschnitt zu berechnen. Eine um den Benutzer nach Zahlen zu fragen. Und die Hauptfunktion für den restlichen Code:

Code: Alles auswählen

#!/usr/bin/env python3


def berechne_durchschnitt(werte):
    summe = sum(werte)
    return (summe, summe / len(werte))


def erfrage_zahlen(bezeichnung_einzahl, bezeichnung_mehrzahl):
    zahlen = list()
    for _ in range(int(input(f'Wieviele {bezeichnung_mehrzahl}: '))):
        zahlen.append(int(input(f'Bitte {bezeichnung_einzahl} eingeben: ')))
    print(zahlen)
    return zahlen


def main():
    zahlen = erfrage_zahlen('Zahl', 'Zahlen')
    noten = [1] + erfrage_zahlen('Note', 'Noten')

    zahlendurchschnitt = berechne_durchschnitt(zahlen)
    notendurchschnitt = berechne_durchschnitt(noten)

    print('`Zahlen` enthält die Werte', zahlen)
    print('Der Durchschnitt von `Zahlen` beträgt:', zahlendurchschnitt)
    print('`Noten` enthält die Werte', noten)
    print('Der Durchschnitt von `Noten` beträgt', notendurchschnitt)


if __name__ == '__main__':
    main()
Wenn man sich das anschaut was übrig bleibt und in der Hauptfunktion landet, dann sieht man wieder ein Muster. Da wird immer abwechselnd etwas für allgemeine Zahlen und für Noten gemacht. Wenn man das so umordnet, das erst alles für Zahlen und dann alles für Noten in der Funktion steht, sieht man, dass beides fast gleich ist, und sich nur geringfügig voneinander unterscheidet:

Code: Alles auswählen

def main():
    zahlen = erfrage_zahlen('Zahl', 'Zahlen')
    zahlendurchschnitt = berechne_durchschnitt(zahlen)
    print('`Zahlen` enthält die Werte', zahlen)
    print('Der Durchschnitt von `Zahlen` beträgt:', zahlendurchschnitt)
    
    noten = [1] + erfrage_zahlen('Note', 'Noten')
    notendurchschnitt = berechne_durchschnitt(noten)
    print('`Noten` enthält die Werte', noten)
    print('Der Durchschnitt von `Noten` beträgt', notendurchschnitt)
Hier lassen sich die Unterschiede aus den beiden Abschnitten als Daten herausziehen und in einer Schleife abarbeiten:

Code: Alles auswählen

def main():
    for anfangswerte, bezeichnung_einzahl, bezeichnung_mehrzahl in [
        ([], 'Zahl', 'Zahlen'), ([1], 'Note', 'Noten')
    ]:
        werte = (
            anfangswerte
            + erfrage_zahlen(bezeichnung_einzahl, bezeichnung_mehrzahl)
        )
        durchschnitt = berechne_durchschnitt(werte)
        print(f'`{bezeichnung_mehrzahl}` enthält die Werte {werte}')
        print(
            f'Der Durchschnitt von `{bezeichnung_mehrzahl}` beträgt:'
            f' {durchschnitt}'
        )
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Cortez
User
Beiträge: 115
Registriert: Montag 31. Dezember 2018, 15:28

Danke dir erstmal für deine große Mühe.
Ich werde nun versuchen, das Ganze mal nachzuvollziehen.

Mein Code war jetzt auch nicht als vollständiges Programm gedacht, sondern ich habe mal verschiedene Dinge ausprobiert. Das Ganze soll allerdings mal ein Programm werden, mit dem man Mitglieder eines Sportvereins verwalten kann. Und da dann nicht nur einer angezeigt werden soll, plane ich irgendwann das Ganze in einer GUI anzeigen zu lassen, wo man dann ein Vereinsmitglied anzeigen lassen kann und die ganzen Berechnungen dann irgendwo im Programm angezeigt werden.
Und für dieses langfristige Ziel dachte ich halt, wäre es von Vorteil, wenn jedes Vereinsmitglied ein Objekt einer Klasse ist.
Wie man diese Daten dann speichert und wieder ausliest, muss ich ohnehin noch erforschen (nehme an, da braucht man zusätzlich auch noch ein paar sql-Befehle).
Aber, wie gesagt, ich programmiere erst seit wenigen Tagen und versuche, mich Schritt für Schritt voranzutasten :)
Benutzeravatar
__blackjack__
User
Beiträge: 14036
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Cortez: Ja, eine Klasse für ein Vereinsmitglied kann Sinn machen. Aber da hat man dann ja auch Daten die zusammen gehören. Zu Klassen gehört halt auch zu lernen wann die *keinen* Sinn machen und das man sie dann *nicht* verwendet. Ein Programm wird nicht automatisch objekorientiert weil man alles irgendwie in Klassen stopft. Klasse kommt im Wort „objektorientiert“ auch gar nicht vor – es gibt sogar objektorientierte Programmiersprachen da gibt es gar keine Klassen, sondern nur Objekte.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten