Instanzmethode wird nicht gefunden

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
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Hallo zusammen,
ich fange gerade mit Python an und verstehe einfach nicht, warum die Klassenmethode "testen" nicht gefunden wird.

Die Fehlermeldung lautet:
AttributeError: 'Datenbank' object has no attribute 'testen'
(es wundert mich schon, dass die Fehlermeldung auf ein Attribut hinweist...)
Die IDE (Spyder) erkennt die Methode, dennoch muss ich irgendetwas mit "db." übersehen...


Python: 3.4.3 (Anaconda 2.3.0)
Mac OsX 10.10.5

Zum Testen der Klasse verwende ich ein Modul namens Test.py:

Code: Alles auswählen

import Datenbank

db = Datenbank.Datenbank('testdatei.db')
db.testen()
Die Klasse:
Der SQL Teil dient nur zu Testzwecken und ist nicht Teil des späteren Programms, daher bitte ignorieren.

Code: Alles auswählen

import sqlite3

class Datenbank:
    def __init__(self, datenbankname):
        self.datei = datenbankname
        self.connection = sqlite3.connect(datenbankname)
    
    def testen(self):
        self.cursor = self.connection.cursor()
        self.sql_command = """
        CREATE TABLE employee ( 
        staff_number INTEGER PRIMARY KEY, 
        fname VARCHAR(20), 
        lname VARCHAR(30), 
        gender CHAR(1), 
        joining DATE,
        birth_date DATE);"""

        self.cursor.execute(self.sql_command)

        self.sql_command = """INSERT INTO employee (staff_number, fname, lname, gender, birth_date)
        VALUES (NULL, "William", "Shakespeare", "m", "1961-10-25");"""
        self.cursor.execute(self.sql_command)


        self.sql_command = """INSERT INTO employee (staff_number, fname, lname, gender, birth_date)
        VALUES (NULL, "Frank", "Schiller", "m", "1955-08-17");"""
        self.cursor.execute(self.sql_command)

        # never forget this, if you want the changes to be saved:
        self.connection.commit()

        self.connection.close()
Vielen Dank für Eure Hilfe oder zumindest einen Wink in die richtige Richtung...

die komplette Fehlermeldung: (einige Zeilen habe ich entfernt, daher stimmen die Zeilennummern nicht)
>>> runfile('/Users/Roger/anaconda/Nebenkosten/Test.py', wdir='/Users/Roger/anaconda/Nebenkosten')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/Roger/anaconda/lib/python3.4/site-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 685, in runfile
execfile(filename, namespace)
File "/Users/Roger/anaconda/lib/python3.4/site-packages/spyderlib/widgets/externalshell/sitecustomize.py", line 85, in execfile
exec(compile(open(filename, 'rb').read(), filename, 'exec'), namespace)
File "/Users/Roger/anaconda/Nebenkosten/Test.py", line 11, in <module>
db.testen()
AttributeError: 'Datenbank' object has no attribute 'testen'
Zuletzt geändert von RogerWilco77 am Sonntag 27. September 2015, 21:54, insgesamt 3-mal geändert.
BlackJack

@RogerWilco77: Verstehe ich auch nicht. Bist Du sicher das Du die Datei gespeichert hast und die Methode nicht nur in der ungespeicherten Datei im Editor existiert? Hast Du noch andere `Datenbank`-Module/Klassen?

Falls Dich das Wort Attribut wundern sollte: Alles was man mit dem Punktoperator von Objekten abfragen kann sind Attribute. Es gibt an der Stelle keinen relevanten Unterschied zwischen ”Daten” und ”Methoden”, denn auch eine Methode ist ein Wert wie jeder andere. In Python ist halt alles was man an einen Namen binden kann ein Objekt.

In der `testen()`-Methode wird viel zu viel an das Objekt gebunden. Da sollte überhaupt kein Attribut neu eingeführt werden, das sollten alles lokale Namen sein.

Die Code- und Datenwiederholung für die beiden Datensätze ist unschön. Man würde da auch die Daten von der SQL-Anweisung trennen und in der SQL-Anweisung durch Platzhalter ersetzen. Dann kann man auch `executemany()` verwenden um beide Datensätze mit einerm Aufruf hinzuzufügen.

Auch bei Datenbankspalten sollte man vernünftige Namen wählen und nicht willkürlich nicht allgemein gebräuchliche Abkürzungen verwenden. Gerade im Hinblick auf ORMs müsste man sonst mit unschönen Attributnamen leben, oder die extra nochmal auf einen sprechenden Namen abbilden. (Apropos ORM, als nächstes sollte man sich nach SQL-Grundlagen dann mit SQLAlchemy auseinander setzen, statt sich das selber nochmal nachzuprogrammieren. :-))

Die numerische, künstliche Primärschlüsselspalte heisst üblicherweise `id`. Und statt `NULL` als Wert anzugeben, könnte man die Spalte beim Eintrage auch einfach weglassen.
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Danke für die schnelle Antwort.
Ich habe den Code für den Datenbankteil erstmal von http://www.python-kurs.eu/sql_python.php übernommen.
Es ging mir erstmal darum irgendwas in irgendeine Datenbank zu schreiben.
Sollte die Verbindung nicht stehen, hätte ich mit einer entsprechenden Fehlermeldung weiter arbeiten können.

Beide Dateien sind definitiv gespeichert und auch über den Finder/Explorer auffindbar.
Ich kann nochmal die gesamte Fehlermeldung oben ergänzen....
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@RogerWilco77: die verlinkten Seite ist nicht zu empfehlen. Die anderen "Kurs"-Teile sind genauso schlecht. Was passiert wenn Du Test.py direkt, nicht aus der IDE heraus startest?
BlackJack

@RogerWilco77: Der Code auf der Webseite steht allerdings nicht sinnloserweise in einer Klasse. Klassen sind kein Selbstzweck, man sollte sie nur einsetzen wenn man das gleiche nicht deutlich einfacher mit einer Funktion machen kann. Willst Du die Verbindung in der `test()`-Methode tatsächlich schliessen‽ Dann ist die Klasse noch sinnfreier als sie das ohnehin schon ist.

Die Begrenzungen von Vor- und Nachname sind IMHO ein bisschen zu klein. Es gibt Leute mit mehreren Vornamen und langen Doppelnamen als Nachnamen. Und das jetzt nur für unseren Kulturkreis gesehen.

Das ganze in ein bisschen vernünftiger, wobei die Klasse auch hier noch nicht wirklich Sinn macht, da sie nur die Verbindung kapselt und eine einzige tatsächliche Methode bietet, was man wie schon gesagt auch einfach mit einer Funktion lösen kann:

Code: Alles auswählen

import sqlite3
from contextlib import closing
from sqlite3 import Date


class Database(object):

    def __init__(self, filename):
        self.connection = sqlite3.connect(filename)
    
    def __enter__(self):
        return self

    def __exit__(self, *_args):
        self.close()
    
    def close(self):
        self.connection.close()

    def test(self):
        with closing(self.connection.cursor()) as cursor:
            try:
                cursor.execute(
                    'CREATE TABLE employee ('
                    ' id INTEGER PRIMARY KEY,'
                    ' firstname VARCHAR(200) NOT NULL,'
                    ' lastname VARCHAR(200) NOT NULL,'
                    " gender CHAR(1) NOT NULL DEFAULT '?'"
                        " CHECK (gender IN ('m', 'f', '?')),"
                    ' joined DATE NOT NULL DEFAULT CURRENT_DATE,'
                    ' birth_date DATE NOT NULL'
                    ');'
                )
                cursor.executemany(
                    'INSERT INTO employee (firstname, lastname, gender,'
                        ' birth_date)'
                    ' VALUES (?, ?, ?, ?);',
                    [
                        ['William', 'Shakespeare', 'm', Date(1961, 10, 25)],
                        ['Frank', 'Schiller', 'm', Date(1955, 8, 17)],
                    ]
                )
                self.connection.commit()
            except:
                self.connection.rollback()
                raise


def main():
    with Database('test.db') as database:
        database.test()

 
if __name__ == '__main__':
    main()
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Sorry, vielleicht war es wirklich eine ungünstige Wahl.
Eigentlich wollte ich nachher damit Nebenkosten erfassen und kein Adressbuch.
Es ging mir erstmal darum eine Datenbankverbindung zu öffnen.
Ich hätte den Teil besser komplett herausgenommen oder abgekürzt.
Entschuldigt bitte die Verwirrung.

Die Datenbank wurde angelegt, daher wollte ich nur sehen, ob auch etwas hinein geschrieben wird.
Der Inhalt war mir erstmal vollkommen egal.
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Sirius3 hat geschrieben: Was passiert wenn Du Test.py direkt, nicht aus der IDE heraus startest?
@Sirius3
In diesem Fall ergibt es
NameError: name 'Test' is not defined
Nachtrag:
Nachdem ich heute neu gestartet und der Empfehlung von Sirius gefolgt bin, habe ich es nochmal in Spyder probiert.
Und siehe da: es funktioniert.
Aber warum?
Zuletzt geändert von RogerWilco77 am Montag 28. September 2015, 19:53, insgesamt 1-mal geändert.
BlackJack

@RogerWilco77: Was hast Du denn da gestartet? Oder versuchst Du `Test.py` in einer *Python*-Shell einzugeben? Das geht natürlich nicht, denn die interpretiert das natürlich als Python-Code und nicht als Dateiname der gestartet werden soll. Du musst das aus einer Shell Deines Betriebssystems aus starten.
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Nein, mit python3 eine Konsole geöffnet und dann Test.py
BlackJack

@RogerWilco77: Naja das ist halt falsch. Einfach *nur* eine Konsole öffnen und da drin dann Test.py starten.
RogerWilco77
User
Beiträge: 8
Registriert: Sonntag 27. September 2015, 20:33

Ups... natürlich:

$python3

>>>import Test.py
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/Axel/anaconda/Nebenkosten/Test.py", line 11, in <module>
db.testen()
File "/Users/Axel/anaconda/Nebenkosten/Datenbank.py", line 26, in testen
self.cursor.execute(self.sql_command)
sqlite3.OperationalError: table employee already exist
... und hier wurde jetzt offenbar eine Verbindung zur Datenbank erstellt.
Aber das hat mittlerweile auch in Spyder funktioniert.
Offenbar hat sich zwischen gestern Abend und heute etwas verändert (von einem Neustart abgesehen?).

PS:
Danke, dass Du nicht direkt die Lösung geschrieben hast. So muss man doch mal nachdenken... :-D
BlackJack

@RogerWilco77: Noch mal etwas zum verlinkten Tutorial: Da wird etwas gemacht was man wirklich *nie* machen sollte: Werte selbst als Zeichenkette in eine SQL-Anweisung hineinformatieren. Das ist gefährlich im Sinne von wenn man Pech hat fällt das auf die Nase bis zu wenn man ganz viel Pech hat nutzt das jemand als Angriffsfläche für eine SQL-Injection.

Die Werte im Tutorial sind auch nicht so ganz nachvollziehbar. In der Abfrage am Ende gibt es zwei Datensätze die vorher nie eingetragen wurden und aus „Jane Wall“ ist plötzlich „Jane Thunder“ geworden.

SQLAlchemy hatte ich ja schon mal erwähnt und möchte dafür nochmal ”Werbung” machen. Das vereinfacht einiges und man muss keine SQL-Anweisungen mehr aus Zeichenketten zusammenstückeln, bekommt also ein robusteres Programm:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from datetime import date as Date

from dateutil.relativedelta import relativedelta
from sqlalchemy import CHAR, Column, create_engine, DATE, INTEGER, VARCHAR
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base


Base = declarative_base()


class Employee(Base):

    __tablename__ = 'employee'

    id = Column(INTEGER, primary_key=True)
    firstname = Column(VARCHAR(200), nullable=False)
    lastname = Column(VARCHAR(200), nullable=False)
    gender = Column(CHAR(1), nullable=False, default='?')
    joined = Column(DATE, nullable=False, default=Date.today)
    birth_date = Column(DATE, nullable=False)

    @property
    def age(self):
        return relativedelta(Date.today(), self.birth_date).years


def print_employees(employees):
    for employee in employees:
        print(
            '  {0.firstname} {0.lastname} ({0.age}),'
            ' geb. {0.birth_date:%d.%m.%Y}'.format(employee)
        )
    print()


def main():
    # 
    # Datenbank-Engine erstellen: Hier kann man sehr einfach die URL austauschen
    # und damit ein andere DBMS wählen ohne das man am Rest des Beispiels etwas
    # ändern muss.
    # 
    engine = create_engine('sqlite:///test.db')
    # 
    # Alle Tabellen für von `Base` abgeleitete Klassen erstellen sofern die
    # Tabelle nicht bereits existiert:
    # 
    Base.metadata.create_all(engine)
    
    session = Session(engine)
    # 
    # Ein paar Daten eintragen:
    # 
    for firstname, lastname, gender, birth_date in [
        ('William', 'Shakespeare', 'm', Date(1961, 10, 25)),
        ('Frank', 'Schiller', 'm', Date(1955, 8, 17)),
        ('Jane', 'Wall', 'f', Date(1989, 3, 14)),
    ]:
        session.add(
            Employee(
                firstname=firstname,
                lastname=lastname,
                gender=gender,
                birth_date=birth_date,
            )
        )
    session.commit()
    # 
    # Jane Wall wird Profiwrestlerin und ändert ihren Nachnamen in „Thunder“:
    # 
    jane = (
        session.query(Employee)
            .filter_by(firstname='Jane', lastname='Wall')
            .one()  # Wenn es mehr als einen Datensatz gibt auf den das zutrifft
                    # dann führt `one()` zu einer Ausnahme!
    )
    jane.lastname = 'Thunder'
    session.commit()

    # 
    # Ein paar Abfragen:
    # 
    employee_query = session.query(Employee)

    print('Alle Datensätze (nach Nachname sortiert):')
    print_employees(employee_query.order_by(Employee.lastname))

    print('Nur männliche Angestellte:')
    print_employees(employee_query.filter_by(gender='m'))

    print('Alle ab 1960 geborenen:')
    print_employees(
        employee_query.filter(Employee.birth_date >= Date(1960, 1, 1))
    )

    # 
    # Tabelle(n) wieder entfernen (sofern sie vorhanden sind):
    # 
    Base.metadata.drop_all(engine)


if __name__ == '__main__':
    main()
Ausgabe:

Code: Alles auswählen

Alle Datensätze (nach Nachname sortiert):
  Frank Schiller (60), geb. 17.08.1955
  William Shakespeare (53), geb. 25.10.1961
  Jane Thunder (26), geb. 14.03.1989

Nur männliche Angestellte:
  William Shakespeare (53), geb. 25.10.1961
  Frank Schiller (60), geb. 17.08.1955

Alle ab 1960 geborenen:
  William Shakespeare (53), geb. 25.10.1961
  Jane Thunder (26), geb. 14.03.1989
Antworten