Ich versuche nur, eine meiner Klassen zu rationalisieren, und habe einige Funktionen in demselben Stil wie das flyweight-Designmuster eingeführt.
Ich bin jedoch etwas verwirrt, warum __init__
immer nach __new__
aufgerufen wird. Ich habe das nicht erwartet. Kann mir jemand sagen, warum das so ist und wie ich diese Funktionalität anders implementieren kann? (Abgesehen von der Implementierung der Implementierung in den __new__
, der sich ziemlich hackig anfühlt.)
Hier ist ein Beispiel:
class A(object):
_dict = dict()
def __new__(cls):
if 'key' in A._dict:
print "EXISTS"
return A._dict['key']
else:
print "NEW"
return super(A, cls).__new__(cls)
def __init__(self):
print "INIT"
A._dict['key'] = self
print ""
a1 = A()
a2 = A()
a3 = A()
Ausgänge:
NEW
INIT
EXISTS
INIT
EXISTS
INIT
Warum?
Verwenden Sie _NEW_, wenn Sie .__ steuern möchten. die Erstellung einer neuen Instanz. Benutzen _INIT_, wenn Sie die Initialisierung einer neuen Instanz steuern müssen.
_NEW_ ist der erste Schritt der Instanzerstellung. Es heißt zuerst und ist verantwortlich für die Rücksendung eines neuen Instanz Ihrer Klasse. Im Gegensatz, _INIT_ gibt nichts zurück; es ist nur für die Initialisierung von .__ verantwortlich. Instanz, nachdem es erstellt wurde.
Im Allgemeinen sollten Sie nicht überschreiben Sie _NEW_, es sei denn, Sie sind Unterklasse eines unveränderlichen Typs wie str, int, unicode oder tuple.
Von: http://mail.python.org/pipermail/tutor/2008-April/061426.html
Sie sollten bedenken, dass das, was Sie zu tun versuchen, normalerweise mit einer Factory gemacht wird. Die Verwendung von _NEW_ ist keine gute, saubere Lösung. Bitte berücksichtigen Sie die Verwendung einer Fabrik. Hier haben Sie ein gutes Fabrikbeispiel .
__new__
ist eine statische Klassenmethode, während __init__
eine Instanzmethode ist. __new__
muss zuerst die Instanz erstellen, damit __init__
sie initialisieren kann. Beachten Sie, dass __init__
self
als Parameter verwendet. Bis zum Erstellen einer Instanz gibt es kein self
.
Nun versuche ich, dass Sie versuchen, Singleton Pattern in Python zu implementieren. Dafür gibt es mehrere Möglichkeiten.
Ab Python 2.6 können Sie außerdem class decorators verwenden.
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
In den meisten bekannten OO - Sprachen weist ein Ausdruck wie SomeClass(arg1, arg2)
eine neue Instanz zu, initialisiert die Attribute der Instanz und gibt sie zurück.
In den meisten bekannten Sprachen OO kann der Teil "Attribute der Instanzinstanz initialisieren" für jede Klasse angepasst werden, indem ein Konstruktor definiert wird, der im Grunde nur ein Codeblock ist, der auf die neue Klasse angewendet wird Instanz (mit den für den Konstruktorausdruck bereitgestellten Argumenten), um die gewünschten Anfangsbedingungen festzulegen. In Python entspricht dies der __init__
-Methode der Klasse.
Pythons __new__
ist nicht mehr und nicht weniger als eine ähnliche Anpassung der Klasse "Zuordnen einer neuen Instanz" pro Klasse. Auf diese Weise können Sie ungewöhnliche Dinge ausführen, z. B. eine vorhandene Instanz zurückgeben, anstatt eine neue Instanz zuzuweisen. In Python sollten wir diesen Teil nicht unbedingt als notwendigerweise mit Zuteilung betrachten. Alles, was wir benötigen, ist, dass __new__
von irgendwoher eine passende Instanz findet.
Aber es ist immer noch nur die Hälfte des Jobs, und das Python-System hat keine Möglichkeit zu wissen, dass Sie manchmal die andere Hälfte des Jobs (__init__
) ausführen möchten und manchmal nicht. Wenn Sie dieses Verhalten wünschen, müssen Sie dies ausdrücklich sagen.
Oft können Sie einen Refactor durchführen, so dass Sie nur __new__
benötigen, oder Sie brauchen __new__
nicht, oder __init__
verhält sich für ein bereits initialisiertes Objekt anders. Wenn Sie wirklich wollen, können Sie in Python "den Job" neu definieren, so dass SomeClass(arg1, arg2)
nicht unbedingt __new__
gefolgt von __init__
aufruft. Dazu müssen Sie eine Metaklasse erstellen und deren __call__
-Methode definieren.
Eine Metaklasse ist nur die Klasse einer Klasse. Die __call__
-Methode einer Klasse steuert, was passiert, wenn Sie Instanzen der Klasse aufrufen. Eine metaclass '__call__
-Methode steuert also, was passiert, wenn Sie eine Klasse aufrufen. Das heißt, Sie können den Instanzerstellungsmechanismus von Anfang bis Ende neu definieren. Dies ist die Ebene, auf der Sie einen vollständig nicht standardmäßigen Instanzerstellungsprozess wie das Singleton-Muster am elegantesten implementieren können. Tatsächlich können Sie mit weniger als 10 Zeilen Code eine Singleton
-Metaklasse implementieren, die Sie nicht einmal dazu zwingt, mit __new__
überhaupt zu futzeln und any ansonsten normale Klassen zu machen Ein Singleton durch einfaches Hinzufügen von __metaclass__ = Singleton
!
class Singleton(type):
def __init__(self, *args, **kwargs):
super(Singleton, self).__init__(*args, **kwargs)
self.__instance = None
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kwargs)
return self.__instance
Dies ist jedoch wahrscheinlich tiefere Magie, als für diese Situation wirklich gerechtfertigt ist!
Um die Dokumentation zu zitieren :
Typische Implementierungen erstellen eine neue Instanz der Klasse durch Aufrufen von Die __new () - Methode der Superklasse verwendet "super (currentclass, cls) .__ new __ (cls [ ...])" mit den entsprechenden Argumenten und dann. Ändern Sie die neu erstellte Instanz nach Bedarf, bevor Sie sie zurückgeben.
...
Wenn __new __ () keine Instanz von cls zurückgibt, dann die neue Die __init __ () - Methode der Instanz wird nicht aufgerufen.
__new __ () soll hauptsächlich Unterklassen von unveränderlichen zulassen Typen (wie int, str oder Tuple) zum Anpassen der Instanzerstellung.
Mir ist klar, dass diese Frage ziemlich alt ist, aber ich hatte ein ähnliches Problem ... Das Folgende tat, was ich wollte:
class Agent(object):
_agents = dict()
def __new__(cls, *p):
number = p[0]
if not number in cls._agents:
cls._agents[number] = object.__new__(cls)
return cls._agents[number]
def __init__(self, number):
self.number = number
def __eq__(self, rhs):
return self.number == rhs.number
Agent("a") is Agent("a") == True
Ich habe diese Seite als Ressource verwendet http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
Wenn __new__
eine Instanz derselben Klasse zurückgibt, wird __init__
für das zurückgegebene Objekt ausgeführt. Das heißt Sie können __new__
NICHT verwenden, um zu verhindern, dass __init__
ausgeführt wird. Selbst wenn Sie ein zuvor erstelltes Objekt aus __new__
zurückgeben, wird es immer wieder doppelt (dreifach usw.) mit __init__
initialisiert.
Hier ist der generische Ansatz des Singleton-Musters, der die vartec-Antwort oben erweitert und korrigiert:
def SingletonClass(cls):
class Single(cls):
__doc__ = cls.__doc__
_initialized = False
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self, *args, **kwargs):
if self._initialized:
return
super(Single, self).__init__(*args, **kwargs)
self.__class__._initialized = True # Its crucial to set this variable on the class!
return Single
Vollständige Geschichte ist hier .
Ein anderer Ansatz, der __new__
beinhaltet, ist die Verwendung von Klassenmethoden:
class Singleton(object):
__initialized = False
def __new__(cls, *args, **kwargs):
if not cls.__initialized:
cls.__init__(*args, **kwargs)
cls.__initialized = True
return cls
class MyClass(Singleton):
@classmethod
def __init__(cls, x, y):
print "init is here"
@classmethod
def do(cls):
print "doing stuff"
Bitte beachten Sie, dass Sie bei diesem Ansatz ALLE Methoden mit @classmethod
schmücken müssen, da Sie niemals eine echte Instanz von MyClass
verwenden.
Ich denke, die einfache Antwort auf diese Frage ist, dass, wenn __new__
einen Wert zurückgibt, der denselben Typ wie die Klasse hat, die __init__
-Funktion ausgeführt wird. In diesem Fall gibt Ihr CodeA._dict('key')
zurück, das dieselbe Klasse wiecls
ist, daher wird __init__
ausgeführt.
class M(type):
_dict = {}
def __call__(cls, key):
if key in cls._dict:
print 'EXISTS'
return cls._dict[key]
else:
print 'NEW'
instance = super(M, cls).__call__(key)
cls._dict[key] = instance
return instance
class A(object):
__metaclass__ = M
def __init__(self, key):
print 'INIT'
self.key = key
print
a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')
ausgänge:
NEW
INIT
NEW
INIT
EXISTS
NB Als Nebeneffekt wird die M._dict
-Eigenschaft automatisch von A
als A._dict
verfügbar gemacht. Achten Sie darauf, sie nicht zufällig zu überschreiben.
__new__ sollte eine neue, leere Instanz einer Klasse zurückgeben. __init__ wird dann aufgerufen, um diese Instanz zu initialisieren. Sie rufen nicht __init__ im "NEUEN" Fall von __new__ an, also wird es für Sie angerufen. Der Code, der __new__
aufruft, verfolgt nicht, ob __init__ für eine bestimmte Instanz aufgerufen wurde oder nicht, und sollte dies auch nicht, weil Sie hier etwas sehr Ungewöhnliches tun.
Sie können dem Objekt in der Funktion __init__ ein Attribut hinzufügen, um anzuzeigen, dass es initialisiert wurde. Überprüfen Sie das Vorhandensein dieses Attributs als Erstes in __init__ und fahren Sie nicht weiter fort, wenn dies der Fall ist.
Als Antwort auf die Antwort von @AntonyHatchkins möchten Sie wahrscheinlich für jede Klasse des Metatyps ein separates Wörterbuch mit Instanzen. Dies bedeutet, dass Sie eine __init__
-Methode in der Metaklasse haben sollten, um Ihr Klassenobjekt mit diesem Wörterbuch zu initialisieren, anstatt es global für alle Klassen zu machen .
class MetaQuasiSingleton(type):
def __init__(cls, name, bases, attibutes):
cls._dict = {}
def __call__(cls, key):
if key in cls._dict:
print('EXISTS')
instance = cls._dict[key]
else:
print('NEW')
instance = super().__call__(key)
cls._dict[key] = instance
return instance
class A(metaclass=MetaQuasiSingleton):
def __init__(self, key):
print 'INIT'
self.key = key
print()
Ich habe den ursprünglichen Code mit einer __init__
-Methode aktualisiert und die Syntax in Python 3-Notation geändert (no-arg-Aufruf an super
und metaclass in den Klassenargumenten statt als Attribut).
In jedem Fall ist es wichtig, dass Ihr Klasseninitialisierer (__call__
-Methode) entweder __new__
oder __init__
nicht ausführt, wenn der Schlüssel gefunden wird. Dies ist viel übersichtlicher als die Verwendung von __new__
. Sie müssen das Objekt markieren, wenn Sie den Standardschritt __init__
überspringen möchten.
Man sollte __init__
als einfachen Konstruktor in traditionellen OO Sprachen betrachten. Wenn Sie beispielsweise mit Java oder C++ vertraut sind, wird dem Konstruktor implizit ein Zeiger auf die eigene Instanz übergeben. Im Falle von Java ist dies die Variable this
. Wenn man den für Java generierten Byte-Code untersuchen würde, würde man zwei Aufrufe bemerken. Der erste Aufruf betrifft eine "neue" Methode und der nächste Aufruf die Init-Methode (dh den eigentlichen Aufruf des benutzerdefinierten Konstruktors). Dieser zweistufige Prozess ermöglicht das Erstellen der tatsächlichen Instanz, bevor die Konstruktormethode der Klasse aufgerufen wird, die nur eine andere Methode dieser Instanz ist.
Im Fall von Python ist __new__
eine hinzugefügte Funktion, auf die der Benutzer zugreifen kann. Java bietet diese Flexibilität aufgrund seines typisierten Charakters nicht. Wenn eine Sprache diese Möglichkeit zur Verfügung stellte, könnte der Implementierer von __new__
in dieser Methode viele Dinge tun, bevor er die Instanz zurückgibt, einschließlich der Erstellung einer völlig neuen Instanz eines nicht verbundenen Objekts. Und dieser Ansatz eignet sich besonders für unveränderliche Typen im Fall von Python.
Bei der Unterklasse unveränderlicher integrierter Typen wie Zahlen und Zeichenfolgen... und gelegentlich in anderen Situationen kommt die statische Methode new praktisch. new ist der erste Schritt in der Instanzkonstruktion, aufgerufen vor init.
Die Methode new wird mit der Klasse als .__ aufgerufen. erstes Argument; Ihre Aufgabe ist es, eine neue Instanz von diesem zurückzugeben Klasse.
Vergleichen Sie dies mit init: init wird mit einer Instanz aufgerufen als erstes Argument, und es gibt nichts zurück; es ist Die Verantwortung liegt darin, die Instanz zu initialisieren.
Es gibt Situationen wo eine neue Instanz erstellt wird, ohne init aufzurufen (zum Beispiel , wenn die Instanz aus einer Pickle geladen wird). Es gibt keine Möglichkeit, .__ zu erstellen. eine neue Instanz, ohne new aufzurufen (obwohl Sie in einigen Fällen __. mit dem Aufruf von new einer Basisklasse umgehen können).
Zu dem, was Sie erreichen möchten, gibt es auch in derselben Dokumentation Informationen zum Singleton-Muster
class Singleton(object):
def __new__(cls, *args, **kwds):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(*args, **kwds)
return it
def init(self, *args, **kwds):
pass
sie können diese Implementierung auch aus PEP 318 mit einem Dekorateur verwenden
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
Etwas tiefer hinein zu graben!
Der Typ einer generischen Klasse in CPython ist type
und ihre Basisklasse ist Object
(es sei denn, Sie definieren explizit eine andere Basisklasse wie eine Metaklasse). Die Reihenfolge der Low-Level-Aufrufe finden Sie hier . Die erste aufgerufene Methode ist type_call
, die dann tp_new
und dann tp_init
aufruft.
Das Interessante dabei ist, dass tp_new
die Object
(Basisklasse) neue Methode object_new
aufruft, die einen tp_alloc
(PyType_GenericAlloc
) macht, der den Speicher für das Objekt reserviert :)
Zu diesem Zeitpunkt wird das Objekt im Speicher erstellt und die __init__
-Methode wird aufgerufen. Wenn __init__
nicht in Ihrer Klasse implementiert ist, wird object_init
aufgerufen, und es wird nichts unternommen :)
Dann gibt type_call
nur das Objekt zurück, das an Ihre Variable gebunden ist.
Der __init__
wird nach __new__
aufgerufen, damit der hinzugefügte Code trotzdem aufgerufen wird, wenn Sie ihn in einer Unterklasse überschreiben.
Wenn Sie versuchen, eine Klasse mit einer __new__
-Klasse zu subklassifizieren, kann jemand, dem dies nicht bekannt ist, den __init__
anpassen und den Aufruf an die Unterklasse __init__
weiterleiten. Diese Konvention des Aufrufs von __init__
nach __new__
hilft dabei, wie erwartet zu arbeiten.
Der __init__
muss noch alle Parameter berücksichtigen, die von der Superklasse __new__
benötigt werden. Wenn dies nicht geschieht, wird in der Regel ein klarer Laufzeitfehler erzeugt. Und __new__
sollte *args
und '** kw' wahrscheinlich explizit zulassen, um deutlich zu machen, dass die Erweiterung in Ordnung ist.
Es ist im Allgemeinen eine schlechte Form, sowohl __new__
als auch __init__
in derselben Klasse auf derselben Vererbungsebene zu haben, da das Originalposter das beschriebene Verhalten aufweist.
Ich bin jedoch etwas verwirrt, warum init immer nach new aufgerufen wird.
Ich denke, die C++ - Analogie wäre hier nützlich: (A) new ordnet einfach Speicher für das Objekt zu. Die Instanzvariablen eines Objekts benötigen Speicher, um es zu speichern. Dies ist, was der Schritt new tun würde. -. (B) init initialisiert die internen Variablen des Objekts auf bestimmte Werte (dies könnte sein Standard).
Ich bin jedoch etwas verwirrt, warum
__init__
immer nach__new__
aufgerufen wird.
Kein großer Grund, außer dass es nur so gemacht wird. __new__
ist nicht dafür verantwortlich, die Klasse zu initialisieren, andere Methoden (__call__
, möglicherweise-- ich weiß es nicht genau).
Ich habe das nicht erwartet. Kann mir jemand sagen, warum das so ist und wie ich diese Funktionalität anders implementiere? (Abgesehen von der Implementierung der Implementierung in den
__new__
, der sich ziemlich hackig anfühlt).
Sie könnten __init__
nichts tun, wenn es bereits initialisiert wurde, oder Sie könnten eine neue Metaklasse mit einem neuen __call__
schreiben, der nur bei neuen Instanzen __init__
aufruft und ansonsten nur __new__(...)
zurückgibt.
Jetzt habe ich das gleiche Problem und aus einigen Gründen habe ich mich entschieden, Dekorateure, Fabriken und Metaklassen zu vermeiden. Ich habe es so gemacht:
def _alt(func):
import functools
@functools.wraps(func)
def init(self, *p, **k):
if hasattr(self, "parent_initialized"):
return
else:
self.parent_initialized = True
func(self, *p, **k)
return init
class Parent:
# Empty dictionary, shouldn't ever be filled with anything else
parent_cache = {}
def __new__(cls, n, *args, **kwargs):
# Checks if object with this ID (n) has been created
if n in cls.parent_cache:
# It was, return it
return cls.parent_cache[n]
else:
# Check if it was modified by this function
if not hasattr(cls, "parent_modified"):
# Add the attribute
cls.parent_modified = True
cls.parent_cache = {}
# Apply it
cls.__init__ = _alt(cls.__init__)
# Get the instance
obj = super().__new__(cls)
# Push it to cache
cls.parent_cache[n] = obj
# Return it
return obj
class A(Parent):
def __init__(self, n):
print("A.__init__", n)
class B(Parent):
def __init__(self, n):
print("B.__init__", n)
>>> A(1)
A.__init__ 1 # First A(1) initialized
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1) # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2 # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2 # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
Der einfache Grund ist, dass new zum Erstellen einer Instanz verwendet wird, während init zum Initialisieren der Instanz verwendet wird. Vor dem Initialisieren sollte die Instanz zuerst erstellt werden. Deshalb sollte new vor init aufgerufen werden.