__new__ vs. __init__

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.
Benutzeravatar
name
User
Beiträge: 254
Registriert: Dienstag 5. September 2006, 16:35
Wohnort: Wien
Kontaktdaten:

Jetzt habe ich mich selbst restlos verwirrt, was ist jetzt genau der Unterschied zwischen __new__ und __init__, herausgefunden hab ich schon, das __new__ eine Instanz vom Typ und __init__ eine von der Klasse erstellt, nur warum wirkt sich das wie folgt aus:

Code: Alles auswählen

class Test(object):
    id = 0

    def __init__(self):
        self.id+=1
        print self.id
print "Test"
test = Test()
test1 = Test()
print "--"
class SecondTest(object):
    id = 0
    def __new__(self):
        self.id+=1
        print self.id
print "SecondTest"
test = SecondTest()
test1 = SecondTest()
Test
1
1
--
SecondTest
1
2
Ohloh | Mein Blog | Jabber: segfaulthunter@swissjabber.eu | asynchia – asynchrone Netzwerkbibliothek

In the beginning the Universe was created. This has made a lot of people very angry and has been widely regarded as a bad move.
BlackJack

Der Unterschied ist, das `__new__()` ein echter Konstruktor ist, also ein Objekt erzeugt und zurückgeben muss und `__init__()` nur die Initialisierung eines bereits (z.B. durch `__new__()`) erzeugten Objekts durchführt.

`__new__()` bekommt als erstes Argument die *Klasse* auf der es aufgerufen wird, und `__init__()` ein *Objekt* der Klasse. Deshalb sollte man das Argument bei `__new__()` auch nicht `self` sondern `cls` nennen. Dann wird hoffentlich auch die beobachtete Ausgabe klarer.

Im ersten Fall wird das Attribut von der *Klasse* gelesen, um eins erhöht und an das *Objekt* gebunden. Darum kommt immer 1 heraus.

Im zweiten Fall wird das Attribut von der *Klasse* gelesen, um ein erhöht und auch wieder an die *Klasse* gebunden.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

BlackJack hat geschrieben:Der Unterschied ist, das `__new__()` ein echter Konstruktor ist, also ein Objekt erzeugt und zurückgeben muss und `__init__()` nur die Initialisierung eines bereits (z.B. durch `__new__()`) erzeugten Objekts durchführt.
Das würde ich so nicht ganz stehen lassen, auch wenn der Begriff Konstruktor sehr unglücklich gewählt wurde. Wie aber auch in eigentlich allen anderen OO-Sprachen versteht man unter einem Konstruktor eine Methode, die ein Objekt nach dessen Erzeugung in einen gültigen Zustand versetzt, mit der Erzeugung hat dieser jedoch nichts zu tun.

Was hier als "echter Konstruktor" bezeichnet wird ist vielmehr eine Fabrik. Da nützt es auch nichts mehr diese ab jetzt mit "Konstruktor" oder "echter Konstruktor" zu versehen, da dieser Begriff einfach schon vergeben ist.

Das wollte ich nur noch mal ergänzen, obwohl ich mir ziehmlich sicher bin, dass BlackJack sich dem durchaus voll bewusst ist, aber sicherlich lesen ja auch noch andere mit :-)
BlackJack

`__init__()` ist kein Konstruktor sondern eine Initialisierung, die *nach* dem Konstruktor, entweder dem impliziten oder `__new__()`, aufgerufen wird.

Man kann `__init__()` auf einem bestehenden Objekt aufrufen, ohne das ein neues Objekt erzeugt wird. Auch wenn der Quelltext, den der Programmierer in Sprachen wie C++ oder Java für einen Konstruktor schreibt, das Objekt nur initialisiert, der Aufruf des Konstruktors erzeugt ein neues Objekt.

Konstruktor und Fabrik(funktion) sind relativ austauschbare Begriffe in einer Programmiersprache, in der Klassen aufrufbar sind mit der Semantik, dass so ein Aufruf eine Fabrikfunktion für Objekte dieser Klasse darstellt.
proofy
User
Beiträge: 32
Registriert: Montag 21. März 2011, 12:47

Wie ist denn so die Zusammenarbeit der beiden Funktionen?
Beispiel:

Code: Alles auswählen

class Project(object):

    NumberOfProjects = 0

    def __init__(self, inName = None):
        if inName:
            self.__name = str(inName)
        self.__projectNumber = Project.NumberOfProjects

    def __new__(cls, *args, **kwds):
        cls.NumberOfProjects += 1
        instance = super(Project, cls).__new__(cls)
        instance.__init__(*args, **kwds)
        return instance

    def __del__(self):
        Project.NumberOfProjects -= 1
Ist das Beispiel so korrekt?

Gibt es beim Destruktor auch zwei Varianten für Klasse und Object?

(Edit: Einrückung korrigiert
Edit 2: Klassen Attribut im init)
Zuletzt geändert von proofy am Freitag 6. Mai 2011, 14:37, insgesamt 2-mal geändert.
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

proofy hat geschrieben:Ist das Beispiel so korrekt?
Etwas stimmt nicht mit deiner Einrückung!
lunar

@proofy: Es gibt nur ".__del__()", und das ist kein Destruktor. In Python werden Objekte nie explizit zerstört, mithin kennt die Sprache auch keine Destruktoren so wie es sie in C++ gibt. ".__del__()" wird aufgerufen, bevor das Objekt vom GC zerstört wird, und da der GC als nicht deterministisch angesehen werden muss, ist der Zeitpunkt des Aufrufs von ".__del__()" nicht garantiert. Es ist nicht einmal garantiert, dass ".__del__()" überhaupt unter allen Umständen aufgerufen wird.
BlackJack

@proofy: Das Beispiel ist nicht korrekt, da die `__init__` zweimal aufgerufen wird.

Zusätzlich zu den Anmerkungen über `__del__()`: Die reine Existenz dieser Methode kann unter bestimmten Umständen verhindern, dass ein Objekt jemals zerstört werden kann. Die Methode ist für reines Python ähnlich sinnlos wie `Object.finalize()` in Java. Man sollte sie meiden. Der einzige sinnvolle Einsatzzweck, den ich bis jetzt gefunden habe, ist bei `ctypes` wenn man Ressourcen freigeben muss, die ausserhalb von Python's Speicherverwaltung liegen.

Sonstige Anmerkungen: Die doppelten Unterstriche bei den Attributen sollte man durch einfache ersetzen. Und der `in`-Prefix bei den Argumenten ist IMHO total sinnfrei.
proofy
User
Beiträge: 32
Registriert: Montag 21. März 2011, 12:47

BlackJack hat geschrieben:@proofy: Das Beispiel ist nicht korrekt, da die `__init__` zweimal aufgerufen wird.
Ich wusste ich hab das noch nicht verstanden.
wenn ich also ein Objekt erzeuge mit:

Code: Alles auswählen

p1 = Project("Projekt 1")

wird automatisch __init__ aufgerufen?
Ist das auch so in IronPython?
BlackJack hat geschrieben: `__del__()`: Man sollte sie meiden.
Wie kann ich dann wie im Beispiel etwas mit Klassen Attributen machen, wenn das Objekt gelöscht wird?
BlackJack hat geschrieben:Sonstige Anmerkungen: Die doppelten Unterstriche bei den Attributen sollte man durch einfache ersetzen.
Das mache ich für die bessere Anbindung .NET Welt mit Ironpython, wenn nur die definierten Properties erkannt werden soll.
BlackJack hat geschrieben: Und der `in`-Prefix bei den Argumenten ist IMHO total sinnfrei.
IMHO2, aber es programmieren ja nicht alle mit einer guten IDE ;) und darum habe ich mir das jetzt so angewöhnt.
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

proofy hat geschrieben:wenn ich also ein Objekt erzeuge mit:

Code: Alles auswählen

p1 = Project("Projekt 1")

wird automatisch __init__ aufgerufen?
Ist das auch so in IronPython?
Ja, das ist unabhängig von der Implementierung.
proofy hat geschrieben:
BlackJack hat geschrieben:Und der `in`-Prefix bei den Argumenten ist IMHO total sinnfrei.
IMHO2, aber es programmieren ja nicht alle mit einer guten IDE ;) und darum habe ich mir das jetzt so angewöhnt.
Hä? Was ist das für eine komische Begründung? Was hat die Wahl einer IDE mit der Namensgebung der Objekte zu tun? Es ist doch völlig egal, welche IDE oder welcher Editor verwendet wird; auf das Aussehen deines Quelltextes soll das keinen Einfluss haben.
lunar

@proofy: Man sollte gar nichts machen, wenn ein Objekt gelöscht wird (von einigen Sonderfällen wie ctypes abgesehen). Gerade bei IronPython kannst Du nicht einmal sicher sein, ob ein Objekt zur Laufzeit des Programms überhaupt je gelöscht wird. Wenn Du Dich darauf verlassen musst, oder auch nur darauf reagieren musst, dann ist Dein Quelltext schlicht fehlerhaft.

Um Attribute als privat zu markieren, sollte ein einzelner Unterstrich ausreichen.

Die Begründung zu Typpräfixen bei Namen ist einigermaßen abenteuerlich. Was in Gottes Namen hat denn die IDE damit zu tun?!
proofy
User
Beiträge: 32
Registriert: Montag 21. März 2011, 12:47

@derdon
schlecht gefragt von mir,

Wird automatisch erst __init__ aufgerufen und __init__ ruft automatisch vorher __new__ auf?
Oder wird erst versucht __new__ aufzurufen und wenn nicht gefunden dann __init__?
Oder wird erst automatisch __new__ aufgerufen und dann automatisch nachher __init__?
Oder wird immer nur __init__ automatisch aufgerufen?

@lunar und @derdon
Ich bin ja neu bei python und bin doch wirklich manchmal verwundert in was sich die "altgedienten" Python-Programmierer "verbeißen" können, wenn Stichworte wie IDE und Codestyle auftauchen ;) Aber vielleicht werde ich auch mal so, sobald ich das alles mehr verstanden habe. :roll:
Zur einfachen Handhabung, einfach mal meinen Kommentar vergessen ...
lunar

@proofy: ".__new__()" und ".__init__()" haben per se überhaupt nichts miteinander zu tun. ".__new__()" dient dazu, aus einer Klasse ein Objektexemplar zu erzeugen, und ist daher auch eine Methode einer Klasse. ".__init__()" dagegen ist eine Methode eines Exemplars, und dient dazu, neu erzeugte Exemplare zu initialisieren.

Aufgerufen werden diese Methoden zum entsprechenden Zeitpunkt vom Interpreter.
BlackJack

@proofy: Betrachten wir mal nur "new style"-Klassen. Da wird immer erst `__new__()` aufgerufen. Wenn man das nicht implementiert, dann halt die von `object`. Wenn `__new__()` ein Exemplar der Klasse liefert auf dem es aufgerufen wurde, wird die `__init__()` auf dem Rückgabewert aufgerufen -- sonst nicht. Steht so in der Dokumentation zu __new__(). Womit auch geklärt sein sollte wie sich das bei IronPython, Jython, oder sonst einer Python-Implementierung verhält. Wenn es sich "Python" nennt, sollte das dort besser auch so ablaufen. :-)
proofy
User
Beiträge: 32
Registriert: Montag 21. März 2011, 12:47

__super__() ;) ich komm so langsam dahinter, vielen Dank.

Habe das jetzt mal ausprobiert:

Code: Alles auswählen

class Project(object):

    NumberOfProjects = 0

    def __init__(self, inName = ""):
        if inName:
            self.__name = str(inName)
        self.__projectNumber = Project.NumberOfProjects
        print "__init__ in Project %d" % Project.NumberOfProjects

    def __new__(cls, *args, **kwds):
        instance = super(Project, cls).__new__(cls)
        instance.__name = None
        instance.__projectNumber = -1
        cls.NumberOfProjects += 1
        print "__new__ in Project %d" % Project.NumberOfProjects
        return instance

    def __del__(self):
        print "__del__ in Project %d" % self.__projectNumber
        Project.NumberOfProjects -= 1

    def __repr__(self):
        return "Project: \n{\n Name: %s\n ProjectNumber: %d\n }" % (str(self.Name), self.ProjectNumber)


    @property
    def Name(self):
        return self.__name

    @property
    def ProjectNumber(self):
        return self.__projectNumber

p1 = Project("Projekt 1")
p2 = Project("Projekt zwei")
print p1
print p2
del(p2)
p3 = Project("Project III")
print p3
p4 = Project()
print p4

Und habe jetzt wohl dadurch einen weiteren Sinn für __new__ gefunden.
Wenn man sicher gehen will, dass ein Attribut wirklich existieren soll, sollte man es wohl in der __new__ erzeugen und
__del__ ist wirklich böse und darum wohl auch das Runterzählen in __del__

Hier mal die Ausgaben in cpython:

Code: Alles auswählen

bash-3.2$ python klasseverstehen.py 
__new__ in Project 1
__init__ in Project 1
__new__ in Project 2
__init__ in Project 2
Project: 
{
 Name: Projekt 1
 ProjectNumber: 1
 }
Project: 
{
 Name: Projekt zwei
 ProjectNumber: 2
 }
__del__ in Project 2
__new__ in Project 2
__init__ in Project 2
Project: 
{
 Name: Project III
 ProjectNumber: 2
 }
__new__ in Project 3
__init__ in Project 3
Project: 
{
 Name: None
 ProjectNumber: 3
 }
__del__ in Project 2
__del__ in Project 1
__del__ in Project 3
und in IronPython:

Code: Alles auswählen

bash-3.2$ ipy klasseverstehen.py 
__new__ in Project 1
__init__ in Project 1
__new__ in Project 2
__init__ in Project 2
Project: 
{
 Name: Projekt 1
 ProjectNumber: 1
 }
Project: 
{
 Name: Projekt zwei
 ProjectNumber: 2
 }
__new__ in Project 3
__init__ in Project 3
Project: 
{
 Name: Project III
 ProjectNumber: 3
 }
__new__ in Project 4
__del__ in Project 2
__init__ in Project 4
Project: 
{
 Name: None
 ProjectNumber: 4
 }
__del__ in Project 4
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

proofy hat geschrieben:Und habe jetzt wohl dadurch einen weiteren Sinn für __new__ gefunden.
Wenn man sicher gehen will, dass ein Attribut wirklich existieren soll, sollte man es wohl in der __new__ erzeugen und
Erm. Und warum sollte es deiner Meinung der Initialisierung in `__init__` ueberlegen sein?
BlackJack

@proofy: Ich schliesse mich der Frage von cofi an.

Ausserhalb vom Ableiten von "immutable" Typen und Metaprogrammierung fällt mir nur noch das Singleton-Entwurfsmuster als Anwendung ein. Von Metaklassen lasse ich in der Regel die Finger, weil man IMHO mit solcher "Magie" vorsichtig und sparsam umgehen sollte. Von nicht veränderbaren Typen leite ich anscheinend nicht ab, und Singletons setze ich um in dem ich einfach von solchen Klassen nur ein Exemplar erstelle und gut ist. Ich glaube ich habe noch nie in "normalem" Quelltext eine `__new__()`-Methode implementiert. Wenn Du das gewohnheitsmässig machst, oder demnächst machen willst, ist das IMHO ziemlich ungewöhnlich. Denn auch in anderer Leute Quelltext habe ich bisher nur sehr vereinzelt `__new__()`-Methoden gesehen.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ich habe __new__ bisher auch nur einmal benutzt http://www.python-forum.de/viewtopic.php?f=1&t=26077, da ich es dort quasi als Klassen-Fabrik nutze, was man sich auch anders hätte lösen können.

Ansonsten schließe ich mich den Vorednern auch an, da ist nichts *überlegenes* an "__new__". Es hat schlicht einen anderen Aufgabenbereich.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
proofy
User
Beiträge: 32
Registriert: Montag 21. März 2011, 12:47

Klassenfabrik ist z.B. eine gute Gelegenheit. Ich meine ja nicht, dass man es dauernd machen soll.
Bei mir war es z.B. aus json direkt ein Objekt aus einer übergebenen Klasse zu erzeugen.
Auch mit der Kommunikation mit "Fremdsystemen" wie .net scheint es sinnvoll zu sein, weil dort die "Fabriken" gerne __new__ aufrufen als __init__ .
lunar

Für solche Zwecke würde ich eher Klassenmethoden einsetzen, e.g. "Foo.from_json(json)". Explizit, weniger magisch und daher meines Erachtens besser lesbar und leichter verständlich.
Antworten