W najbardziej znanych językach OO wyrażenie takie SomeClass(arg1, arg2)
przydzieli nową instancję, zainicjuje atrybuty instancji, a następnie zwróci ją.
W najbardziej znanych językach OO część „inicjowanie atrybutów instancji” można dostosować dla każdej klasy poprzez zdefiniowanie konstruktora , który jest w zasadzie tylko blokiem kodu działającym na nowej instancji (przy użyciu argumentów przekazanych do wyrażenia konstruktora ), aby skonfigurować dowolne warunki początkowe. W Pythonie odpowiada to __init__
metodzie klasy .
Python __new__
jest niczym więcej i niczym innym jak podobnym dostosowaniem dla poszczególnych klas części „przydzielanie nowej instancji”. To oczywiście pozwala robić niezwykłe rzeczy, takie jak zwracanie istniejącej instancji zamiast przydzielania nowej. Tak więc w Pythonie nie powinniśmy naprawdę myśleć o tej części jako o konieczności alokacji; wszystko, czego potrzebujemy, to __new__
wymyślenie odpowiedniej instancji skądś.
Ale wciąż jest to tylko połowa zadania, a system Python nie może wiedzieć, że czasami chcesz uruchomić drugą połowę zadania ( __init__
), a czasem nie. Jeśli chcesz tego zachowania, musisz to powiedzieć wprost.
Często można dokonać refaktoryzacji, aby wystarczyła __new__
lub nie była potrzebna __new__
, lub aby __init__
zachowywała się inaczej na już zainicjowanym obiekcie. Ale jeśli naprawdę chcesz, Python pozwala ci na nowo zdefiniować „zadanie”, więc SomeClass(arg1, arg2)
niekoniecznie musi to być wywołanie, __new__
a następnie __init__
. Aby to zrobić, musisz utworzyć metaklasę i zdefiniować jej__call__
metodę.
Metaklasa to tylko klasa klasy. A __call__
metoda klasy kontroluje, co się stanie, gdy wywołasz instancje klasy. Więc metaklasa " __call__
kontroli sposobu, co się dzieje, kiedy zadzwonić klasę; tzn. pozwala przedefiniować mechanizm tworzenia instancji od początku do końca . Na tym poziomie możesz najbardziej elegancko wdrożyć całkowicie niestandardowy proces tworzenia instancji, taki jak wzorzec singletonu. W rzeczywistości, z mniej niż 10 linii kodu można zaimplementować Singleton
metaklasa że wtedy nawet nie wymaga futz ze __new__
w ogóle , a może zamienić dowolny inny sposób normalny do klasy Singleton poprzez dodanie __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
Jest to jednak prawdopodobnie głębsza magia, niż jest to naprawdę uzasadnione w tej sytuacji!