Django und Klassenvariablen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Irgendwie komme ich mir grad total blöd vor aber ich raffe die Klassendefinitionen bei Django nicht.

Ich habe eine Klasse, die durch Django in eine Datenbanktabelle umgesetzt wird:

Code: Alles auswählen

class User(models.Model):
    """class for managing logged in users"""
    name = models.CharField(maxlength=100, help_text=help_dic["user_name"])
    fullname = models.CharField(maxlength=100, blank=True,
                help_text=help_dic["user_fullname"])
    last_id = models.CharField(maxlength=20, blank=True,
                help_text=help_dic["user_last_id"])
    ignored = models.BooleanField(default=False,
                help_text=help_dic["user_ignored"])

    def __str__(self):
        return "%s (%s)" % (self.name, self.fullname)
Nun habe ich Funktionen, die eine Variable uptime benötigen, diesen Wert möchte ich aber nicht in einer Datenbank speichern und wollte sie nun einfach so der Klasse mitgeben. Nur weiß ich nicht ob ich sie als Klassenvariable definieren muss. Ich habe dann folgendes gemacht:

Code: Alles auswählen

class User(models.Model):
    """class for managing logged in users"""
    name = models.CharField(maxlength=100, help_text=help_dic["user_name"])
    fullname = models.CharField(maxlength=100, blank=True,
                help_text=help_dic["user_fullname"])
    last_id = models.CharField(maxlength=20, blank=True,
                help_text=help_dic["user_last_id"])
    ignored = models.BooleanField(default=False,
                help_text=help_dic["user_ignored"])
    uptime = 0
Nun hat ja jedes Objekt das von der Klasse erstellt wird diese Variable enthalten.

Ich weiß nicht wie Django das intern alles umsetzt, aber wenn ich einfach die Klasse nehme und mir neue Objekte erstelle, dann sieht das ja ungefähr folgendermaßen aus:

Code: Alles auswählen

a = User("user1")
b = User("user2")
print a.uptime, b.uptime
0 0
Wenn ich die Klassenvariable auf "1" ändere, dann steht da:

Code: Alles auswählen

print a.uptime, b.uptime
1 1
Wenn ich nun gezielt eine bestimmte Uptime-Variable ändere, dann passiert folgendes:

Code: Alles auswählen

a.uptime = 44
c = User("user3")
print a.uptime, b.uptime, c.uptime
44 1 1
und wenn ich dann die Klassenvariable auf "123" ändere:

Code: Alles auswählen

print a.uptime, b.uptime, c.uptime
44 123 123
Heißt das sobald ich eine Klassenvariable überschreibe, das diese automatisch neu für das Objekt angelegt wird (also self.uptime)??

Ich bin mir unsicher wie man sowas machen soll, ich habe versucht einfach die Funktion __init__ zu definieren aber dann erwartet die Klasse beim Anlegen 6 Parameter und ich weiß nicht welche.
Habe ich einen Nachteil wenn ich diese Klassenvariable "missbrauche"?
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Hab' keine Ahnung von Django, aber Dein Problem sieht für mich wie ein schieres Python-Problem aus. Die wievielte Python-Klasse ist es denn, die Du da schreibst? Das 6. Argument für __init__() ist, wie bei absolut jeder Python-Klasse, das in der Reihenfolge erste: self.

Das hier:
http://docs.python.org/tut/node11.html
sollte jeder, absolut jeder (inklusive aller möglichen Genies), der Python programmieren will, gelesen haben. Ich lese meine alten Ausdruck davon immer wieder einmal. Der beschriebene Bindungsmechanismus von Methoden an Klassen ist das Besondere an Python, super einfach und gleichzeitig enorme Möglichkeiten bietend (Thematik Metaklassen).
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

joost hat geschrieben:Der beschriebene Bindungsmechanismus von Methoden an Klassen ist das Besondere an Python, super einfach und gleichzeitig enorme Möglichkeiten bietend (Thematik Metaklassen).
Was haben unbound methods mit Metaklassen zu tun?
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Wie gesagt eben war ich wohl ein wenig verwirrt. Eigentlich habe ich schon einige Programme geschrieben und hätte ja selbst drauf kommen können was ich da für einenKäse erzählt habe.
Mir ist klar (eben halt irgendwie nicht :roll: ) das

Code: Alles auswählen

a.x = 1
zum Anlegen eines neuen Attributs führt, falls x noch nicht vorhanden ist oder x ein Klassenattribut ist.
Mein Problem ist aber immer noch was Django von mir erwartet, wenn ich die __init__ implementiere. Ich finde dazu leider nichts in der Doku, da ich nur 4 Attribute habe, er aber 6 Parameter möchte, kann es sich nicht nur noch um self handeln, irgendwas fehlt wohl noch. Ich dachte hier kennt sich vielleicht zufällig jemand mit Django aus...
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

zusammen mit uptime und self komme ich auf 6 Argumente.
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Ich habe uptime wieder draußen... ich muss wohl noch mal das Handbuch zu Django durchstöbern...
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

und @birkenfeld: Ich habe das bisher kaum genutzt (Singletons werde ich in Zukunft vielleicht schon einmal anlegen), aber dass man so etwas in Python einführen konnte und (wenn ich's richtig sehe) gleichzeitig den alten Code eher optimieren, habe ich bisher immer im Zusammenhang mit dem Bindungssystem stehend gesehen.
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Oh mein Gott ich glaube ich gehe mal raus und 'ne Runde joggen, mein Hirn ist ja nur Matsch. Ich weiß nicht mehr was ich vorher falsch gemacht habe, aber ein einfaches

Code: Alles auswählen

def __init__(self, uptime=0):
    self.uptime = uptime
funktioniert wunderbar... Sorry für diesen unnötigen Thread...
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

So jetzt geht es doch nicht, ich mache

Code: Alles auswählen

User.objects.all()
und bekomme folgenden Error:

Code: Alles auswählen

/usr/lib/python2.5/site-packages/django/db/models/query.py in __repr__(self)
    100
    101     def __repr__(self):
--> 102         return repr(self._get_data())
    103
    104     def __len__(self):

/usr/lib/python2.5/site-packages/django/db/models/query.py in _get_data(self)
    468     def _get_data(self):
    469         if self._result_cache is None:
--> 470             self._result_cache = list(self.iterator())
    471         return self._result_cache
    472

/usr/lib/python2.5/site-packages/django/db/models/query.py in iterator(self)
    193                                                     index_start=0, max_depth=self._max_related_depth)
    194                 else:
--> 195                     obj = self.model(*row[:index_end])
    196                 for i, k in enumerate(extra_select):
    197                     setattr(obj, k[0], row[index_end+i])

<type 'exceptions.TypeError'>: __init__() takes at most 2 arguments (6 given)
Es geht nur wenn ich ein Objekt per Hand anlege, wenn ich aber alle User aus der Datenbank lesen möchte, dann geht es nicht mehr. Verstehe ich nicht.
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Ja, jetzt wird's vielleicht doch zu Django-spezifisch, ich kanne die Methode objects(...) Deiner User-Klasse nicht. Aber 2 Dinge fallen mir doch auf: Gemäß
class User(models.Model): erbt die Klasse User, an der Du arbeitest, ja von models.Model. In vielen Fällen sieht braucht man dann in einer Init die Initialisierung der geerbten Attribute, was man so macht:

Code: Alles auswählen

class User(models.Model):
    def __init__(self, args1):
        ...
        models.Model.__init__(self, args2)
'args1 und 2' meine ich hier als Abkürzung (oft macht man so ein Durchreichen, d.h. args2 ist eine "Teilmenge" von args1).

Zweitens enthält ist die rechte Seite der bemängelten Zeile 195

Code: Alles auswählen

obj = self.model(*row[:index_end])
ja wohl ein Konstruktor-Aufruf, d.h. die Probleme hängen wirklich mit __init__ selbst zusammen. Der Grund dafür, dass es 'von Hand' geht, könnte darin liegen, dass jede Python-Klasse jederzeit die Hinzufügung weiterer Attribute erlaubt, Du kannst überall im Code (in dem die Klasse User sichtbar ist) User.VersehentlichesAttribut = 'irgendwas' schreiben und hast ein neues Attribut. Dies sollte man eigentlich eher vermeiden, kann aber ein Hilfsmittel sein, globale Variablen zu vermeiden und doch auf schnelle, einfache Art etwas wie deren Funktionalität zu haben, z.B. wenn die Klasse ein Toplevel-Window ist (von Toplevel abgeleitet ist).
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

OK ich war gerade im Django IRC und da hat mir einer erklärt wie es geht, aber so richtig kapieren tue ich es nicht, funktionieren tut es:

Code: Alles auswählen

def __get_uptime(self):
        if hasattr(self, '__uptime'):
            return self.__uptime
        self.__uptime = 0
        return self.__uptime

def __set_uptime(self, value):
    self.__uptime = value

uptime = property(__get_uptime, __set_uptime)
Also mit einem Property soll ich es machen... vielleicht kann mir jemand erklären was das in diesem Fall bringt?
Die __init__ soll ich nicht überschreiben, da Django wohl sehr viel Magic im Hintergrund macht, die man besser nicht anrührt.
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Das bringt hier die Funktionalität, dass uptime - von außen gesehen - immer da ist. Das statement IrgendeineVariable = UserObject.uptime ruft nämlich wegen der property-Sache __get_uptime() auf, die bei Noch-Nicht-Vorhandensein das Attribut erst erzeugt.

hasattr(), getattr(), setattr() und delattr() sind 4 wichtige eingebaute Funktionen, mit denen man von Textvariablen aus auf Attributnamen Zugriff bekommt und so exec-statements, die in den meisten Fällen wesentlich unübersichtlicher ausfallen würden, vermeidet. So kann man zum Beispiel in einer Schleife Labels mit den Namen
Lb0, Lb1, Lb2 ... mit Texten 't[0], t[1], t[2] ... erzeugen, wenn diese Attribute einer Klasse werden sollen:

Code: Alles auswählen

for i in range(0, ende): 
    setattr(IrgendeinObjekt, 'Lb'+str(i), gtk.Label(t[i]))
In Deinem Code tut 'hasattr' tut genau, was Du da siehst - es wird so dafür gesorgt, dass das Attribut uptime der Klasse wirklich höchstens einmal zugefügt wird.
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Irgendwie hat das alles auch noch nicht funktioniert, ich musste __uptime dennoch in der Klasse definieren und hasattr wieder rausnehmen, warum auch immer. So funktioniert es jetzt 100%ig:

Code: Alles auswählen

class User(models.Model):
    """class for managing logged in users"""
    name = models.CharField(maxlength=100, help_text=help_dic["user_name"])
    fullname = models.CharField(maxlength=100, blank=True,
                help_text=help_dic["user_fullname"])
    last_id = models.CharField(maxlength=20, blank=True,
                help_text=help_dic["user_last_id"])
    ignored = models.BooleanField(default=False,
                help_text=help_dic["user_ignored"])

    __uptime = 0

    def __get_uptime(self):
        return self.__uptime

    def __set_uptime(self, value):
        self.__uptime = value

    uptime = property(__get_uptime, __set_uptime)
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Das freut mich für Dich, ABER:

Jetzt ist property hier tatsächlich völlig überflüssig, weil in den zugehörigen set und get nichts mehr getan wird, was normale Attribut-Zuweisungen nicht auch tun.

Der Unterschied zu dem übernommenen Code ist eben der, dass Du uptime in __init__() mit Null initialisierst. Derjenige, der Dir den Rat mit property gegeben hatte, wollte das wohl vermeiden - nur deshalb wurde property für seine get-Funktion nötig (und das war wohl eigentlich sowieso schon oversized für eine Variable, die nur einen int speichert - die kann man ruhig immer anlegen lassen).

Jetzt kannst Du ohne Änderungen nicht nur das hasattr, sondern natürlich auch noch das property einsparen und uptime als normales Attribut verwenden. Versuch das noch einmal ! Wenn Du das nicht tust und diesen Code dann liegen läßt, würdest Du Dich, sähest Du ihn später einmal (sagen wir mal: in zwei Monaten), möglicherweise doch fragen: Was soll denn property hier ? Hab' ich versehentlich irgendwas gelöscht ?
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Mein Code ist nun ein wenig anders und so bin ich auch ganz zufrieden damit:

Code: Alles auswählen

class User(models.Model):
    """class for managing logged in users"""
    name = models.CharField(maxlength=100, help_text=help_dic["user_name"])
    fullname = models.CharField(maxlength=100, blank=True,
                help_text=help_dic["user_fullname"])
    cur_id = models.CharField(maxlength=20, blank=True,
                help_text=help_dic["user_cur_id"])
    last_id = models.CharField(maxlength=20, blank=True,
                help_text=help_dic["user_last_id"])
    ignored = models.BooleanField(default=False,
                help_text=help_dic["user_ignored"])

    def __get_uptime(self):
        """return uptime"""
        tmp = int(snmp_val(_defaultcfg.ip, _defaultcfg.snmp_community,
                _defaultcfg.uptime_mib.oid + str(self.cur_id) + ".0", 1))
        if not tmp:
            return "0:00:00"
        div, mod = divmod(tmp, 8640000)
        div, mod = divmod(mod, 360000)
        uptime_str = '%d:' % div
        div, mod = divmod(mod, 6000)
        uptime_str += '%02d:' % div
        div, mod = divmod(mod, 100)
        uptime_str += '%02d' % div
        return uptime_str
    uptime = property(__get_uptime)

    def __get_minutes(self):
        """return uptime in minutes"""
        hour, minute, second = self.uptime.split(":")
        minutes = (int(hour)*60) + int(minute)
        return minutes
    minutes = property(__get_minutes)
Ja ich weiß auch hier ist vielleicht ein Property unnötig, aber ich mag es einfach das wenn ich uptime abrufe, ich es schon richtig formatiert bekomme und ich nicht irgendwelche Funktionen aufrufen muss. Das ist nun eine Kombi aus 2 Funktionen in einen Getter gepackt.
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Eben nicht, weil diese Funktionen nichts tun - und schon gar keine Formatierung.
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Wieso formatiert die Funktion nichts??
Der Wert kommt per SNMP in einem komischen Zeitformat das ich umwandle, verstehe deinen Punkt nicht.
joost
gelöscht
Beiträge: 134
Registriert: Sonntag 29. April 2007, 13:28

Sorry, dabei war ich noch bei Deinem zum 22.26 geposteten Code.
[color=green][size=75]Never use idle.pyw, if you need sys.stdin[/size][/color]
Benutzeravatar
TheGrudge
User
Beiträge: 96
Registriert: Donnerstag 4. Mai 2006, 18:39

Nochmal eine Frage, wenn ich den Code mit hasattr ausstatte, dann funktioniert das nicht. Kann es sein das man hasattr nicht mit self benutzen kann?

Code: Alles auswählen

def __get_uptime(self):
        """return uptime in a human readable format"""
        if hasattr(self, '__uptime'):
            print "old value..."
            return self.__uptime
        print "No uptime defined, getting new one..."
        tmp = int(snmp_val(_defaultcfg.ip, _defaultcfg.snmp_community,
                _defaultcfg.uptime_mib.oid + str(self.cur_id) + ".0", 1))
        if not tmp:
            self.__uptime = "00:00:00"
        else:
            div, mod = divmod(tmp, 8640000)
            div, mod = divmod(mod, 360000)
            uptime_str = '%d:' % div
            div, mod = divmod(mod, 6000)
            uptime_str += '%02d:' % div
            div, mod = divmod(mod, 100)
            uptime_str += '%02d' % div
            self.__uptime = uptime_str
        return self.__uptime
    uptime = property(__get_uptime)
Die Testausgabe lautet immer:

Code: Alles auswählen

In [7]: a.uptime
No uptime defined, getting new one...
No uptime defined, getting new one...
Out[7]: '10:17:36'

In [8]: a.uptime
No uptime defined, getting new one...
No uptime defined, getting new one...
Out[8]: '10:17:36'
Er sollte beim zweiten Mal ja "Old value..." hinschreiben, oder etwa nicht?
Und warum schreibt er die Ausgabe immer 2 mal hin?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

TheGrudge hat geschrieben:

Code: Alles auswählen

def __init__(self, uptime=0):
    self.uptime = uptime
Ich würde sagen, das ist schon die richtige Lösung. Nur must du mit args und kwargs arbeiten und die eigentliche __init__ Methode mit super() aufrufen. Dann sollte das gehen, denke ich...

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