programowanieStarszy programista Python

Jak działa metoda __new__ w Pythonie i jak różni się od __init__? Kiedy i dlaczego należy przesłonić __new__, a nie __init__? Podaj szczegółowy przykład użycia.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Pythonie tworzenie obiektu klasy to proces dwuetapowy:

  1. Najpierw wywoływana jest metoda statyczna __new__, która odpowiada za przydzielenie i zwrócenie nowej instancji klasy.
  2. Następnie wywoływana jest __init__, która inicjalizuje już utworzony obiekt.

Różnice:

  • __new__ — tworzenie (można zwrócić dowolny nowy obiekt; obowiązkowy parametr — klasa).
  • __init__ — inicjalizacja (pracuje z self, już przydzieloną instancją).
  • Zwykle __new__ jest przesłaniane dla dziedziczących po niemutowalnych typach (tuple, str, int), tworzenia singletonów i wzorca "fabryka".

Przykład użycia:

class MyStr(str): def __new__(cls, value): print("__new__ wywołane") instance = super().__new__(cls, value.upper()) # zmieniamy wartość przed utworzeniem return instance def __init__(self, value): print("__init__ wywołane", value) s = MyStr('abc') # __new__ wywołane -> __init__ wywołane abc print(s) # 'ABC'

Pytanie z pułapką.

Czy można zainicjować niemutowalny obiekt przez __init__, jeśli w klasie nie zrealizowano __new__?

Odpowiedź z przykładem:

Nie! Na przykład dla niemutowalnych typów (jak str, int, tuple) wszelkie zmiany nad polami/wartością muszą odbywać się w __new__, inaczej w __init__ nie można zmienić wartości.

class MyTuple(tuple): def __init__(self, items): print(f'__init__! {items}') def __new__(cls, items): print(f'__new__! {items}') return super().__new__(cls, map(str, items)) t = MyTuple([1, 2, 3]) print(t) # ('1', '2', '3')

Przykłady rzeczywistych błędów z powodu niewiedzy o szczegółach tematu


Historia

W dużym projekcie Django zaimplementowano dziedziczenie po str do przechowywania specjalnych typów ciągów. Próbowano zmieniać wartość w __init__ — ale wynik nie zmieniał się, ponieważ str jest niemutowalny. Naprawiono to dopiero po zbadaniu i przesłonięciu __new__.


Historia

Podczas implementacji wzorca Singleton zapomniano zaimplementować logikę zwracania istniejącej instancji w __new__: kilka wywołań klasy nadal tworzyło nowe obiekty, co prowadziło do fragmentacji pamięci.


Historia

W bibliotece do serializacji danych podczas cachowania użyto klasy z przesłoniętym __init__, ale nie zauważono, że do cachowania potrzebny jest właśnie zwrot tego samego obiektu przez __new__. Z tego powodu cache psuł się przy każdym nowym wywołaniu, ponieważ tworzone były różne obiekty.