Mengapa __init __ () selalu dipanggil setelah __new __ ()?


568

Saya hanya mencoba merampingkan salah satu kelas saya dan telah memperkenalkan beberapa fungsi dengan gaya yang sama dengan pola desain kelas terbang .

Namun, saya agak bingung mengapa __init__selalu dipanggil setelah __new__. Saya tidak mengharapkan ini. Adakah yang bisa memberi tahu saya mengapa ini terjadi dan bagaimana saya bisa mengimplementasikan fungsi ini? (Selain menempatkan implementasi ke dalam __new__yang terasa cukup gila.)

Ini sebuah contoh:

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()

Output:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Mengapa?


mencoba memahami pola desain juga, dan pertama kali mendengar tentang: pola desain kelas terbang .. dan tautan yang sangat baik memiliki contoh di hampir semua bahasa populer.
EmmaYang

Jawaban:


598

Gunakan __new__saat Anda perlu mengontrol pembuatan instance baru.

Gunakan __init__saat Anda perlu mengontrol inisialisasi instance baru.

__new__adalah langkah pertama pembuatan instance. Ini dipanggil pertama, dan bertanggung jawab untuk mengembalikan instance baru dari kelas Anda.

Sebaliknya, __init__tidak mengembalikan apa pun; itu hanya bertanggung jawab untuk menginisialisasi instance setelah dibuat.

Secara umum, Anda tidak perlu mengesampingkan __new__kecuali Anda mensubklasifikasikan jenis yang tidak dapat diubah seperti str, int, unicode, atau tuple.

Dari April 2008 posting: Kapan menggunakan __new__vs __init__? di mail.python.org.

Anda harus mempertimbangkan bahwa apa yang Anda coba lakukan biasanya dilakukan dengan Pabrik dan itulah cara terbaik untuk melakukannya. Menggunakan __new__bukanlah solusi bersih yang baik jadi harap pertimbangkan penggunaan pabrik. Di sini Anda memiliki contoh pabrik yang bagus .


1
Akhirnya menggunakan __new__di dalam kelas Pabrik, yang telah menjadi sangat bersih, jadi terima kasih atas masukan Anda.
Dan

11
Maaf, saya tidak setuju bahwa penggunaan __new__harus dibatasi secara ketat untuk kasus-kasus yang disebutkan. Saya merasa sangat berguna untuk mengimplementasikan pabrik kelas generik yang dapat dikembangkan - lihat jawaban saya atas pertanyaan Penggunaan yang tidak benar __new__untuk menghasilkan kelas dengan Python? untuk contoh melakukannya.
martineau

170

__new__adalah metode kelas statis, sedangkan __init__metode contoh. __new__harus membuat instance terlebih dahulu, jadi __init__dapat menginisialisasi. Catatan yang __init__diambil selfsebagai parameter. Sampai Anda membuat contoh, tidak ada self.

Sekarang, saya kumpulkan, bahwa Anda mencoba menerapkan pola singleton dengan Python. Ada beberapa cara untuk melakukan itu.

Juga, pada Python 2.6, Anda dapat menggunakan dekorator kelas .

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

8
@ Tyler Long, saya tidak mengerti bagaimana cara kerja "@singleton"? Karena dekorator mengembalikan fungsi tetapi MyClass adalah kelas.
Alcott

11
Kenapa kamus? Karena cls akan selalu sama dan Anda mendapatkan kamus baru misalnya untuk setiap singleton Anda membuat kamus dengan hanya satu item di dalamnya.
Winston Ewert

7
@Alcott: Tidak ada pendapat yang diperlukan - dokumen setuju dengan Anda.
Ethan Furman

2
@Alcott. ya dekorator mengembalikan fungsi. tapi baik kelas dan fungsinya bisa dipanggil. Saya pikir instance = {} harus menjadi variabel global.
Tyler Long

4
@TylerLong Alcott punya poin bagus. Ini menghasilkan nama MyClassyang terikat ke fungsi yang mengembalikan instance dari kelas asli. Tetapi sekarang tidak ada cara untuk merujuk ke kelas asli sama sekali, dan MyClassmenjadi fungsi istirahat isinstance/ issubclasspemeriksaan, akses ke atribut / metode kelas secara langsung sebagai MyClass.something, penamaan kelas dalam superpanggilan, dll, dll. Apakah itu masalah atau tidak tergantung pada kelas tempat Anda menerapkannya (dan program lainnya).
Ben

148

Dalam sebagian besar bahasa OO yang terkenal, ekspresi seperti SomeClass(arg1, arg2)akan mengalokasikan instance baru, menginisialisasi atribut instance, dan kemudian mengembalikannya.

Dalam sebagian besar bahasa OO yang terkenal, bagian "inisialisasi atribut instance" dapat dikustomisasi untuk setiap kelas dengan mendefinisikan konstruktor , yang pada dasarnya hanya blok kode yang beroperasi pada instance baru (menggunakan argumen yang disediakan untuk ekspresi konstruktor ) untuk mengatur kondisi awal apa pun yang diinginkan. Dalam Python, ini sesuai dengan metode kelas __init__.

Python __new__tidak lebih dan tidak kurang dari kustomisasi per kelas yang sama dari bagian "alokasikan contoh baru". Ini tentu saja memungkinkan Anda untuk melakukan hal-hal yang tidak biasa seperti mengembalikan contoh yang ada daripada mengalokasikan yang baru. Jadi dengan Python, kita seharusnya tidak benar-benar menganggap bagian ini melibatkan alokasi; semua yang kami butuhkan adalah yang __new__datang dengan contoh yang cocok dari suatu tempat.

Tapi itu masih hanya setengah dari pekerjaan, dan tidak ada cara bagi sistem Python untuk mengetahui bahwa kadang-kadang Anda ingin menjalankan setengah pekerjaan lainnya ( __init__) sesudahnya dan kadang-kadang tidak. Jika Anda menginginkan perilaku itu, Anda harus mengatakannya secara eksplisit.

Seringkali, Anda dapat melakukan refactor sehingga Anda hanya perlu __new__, atau Anda tidak perlu __new__, atau sehingga __init__berperilaku berbeda pada objek yang sudah diinisialisasi. Tetapi jika Anda benar-benar ingin, Python benar-benar memungkinkan Anda untuk mendefinisikan kembali "pekerjaan", jadi itu SomeClass(arg1, arg2)tidak selalu memanggil __new__diikuti __init__. Untuk melakukan ini, Anda perlu membuat metaclass, dan menentukan __call__metodenya.

Metaclass hanyalah kelas dari suatu kelas. Dan metode kelas __call__mengontrol apa yang terjadi ketika Anda memanggil instance kelas. Jadi metaclass ' __call__metode kontrol apa yang terjadi ketika Anda menelepon kelas; yaitu memungkinkan Anda untuk mendefinisikan ulang mekanisme pembuatan instance dari awal hingga selesai . Ini adalah tingkat di mana Anda dapat paling elegan menerapkan proses pembuatan instance yang sepenuhnya tidak standar seperti pola tunggal. Bahkan, dengan kurang dari 10 baris kode Anda dapat menerapkan Singletonmetaclass yang kemudian bahkan tidak mengharuskan Anda untuk futz dengan __new__ sama sekali , dan dapat mengubah setiap kelas lain-normal menjadi tunggal dengan hanya menambahkan __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

Namun ini mungkin sihir yang lebih dalam daripada yang sebenarnya diperlukan untuk situasi ini!


25

Mengutip dokumentasi :

Implementasi umum membuat instance baru dari kelas dengan memanggil metode __new __ () superclass menggunakan "super (currentclass, cls) .__ baru __ (cls [, ...])" dengan argumen yang sesuai dan kemudian memodifikasi instance yang baru dibuat sesuai kebutuhan sebelum mengembalikannya.

...

Jika __baru __ () tidak mengembalikan instance dari cls, maka metode __init __ () instance baru tidak akan dipanggil.

__new __ () dimaksudkan terutama untuk memungkinkan subkelas dari tipe yang tidak dapat diubah (seperti int, str, atau tuple) untuk mengkustomisasi pembuatan instance.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Itu poin penting. Jika Anda mengembalikan contoh yang berbeda, sumber asli __init__tidak pernah dipanggil.
Jeffrey Jose

@ talay Saya menyadari jawaban Anda dari dokumen, tapi saya ingin tahu jika Anda tahu ada gunanya tidak mengembalikan contoh kb. Sepertinya ketika objek yang dikembalikan __new__diperiksa untuk kelasnya, metode akan melempar kesalahan daripada membiarkan cek gagal lulus diam-diam, karena saya tidak mengerti mengapa Anda ingin mengembalikan apa pun kecuali objek kelas .
soporific312

@ soporific312 Saya belum melihat ada kasus penggunaan. Jawaban ini membahas beberapa alasan untuk desain, meskipun mereka juga belum melihat kode yang memanfaatkan "fitur" itu.
tgray

12

Saya menyadari bahwa pertanyaan ini cukup lama tetapi saya memiliki masalah serupa. Berikut ini melakukan apa yang saya inginkan:

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

Saya menggunakan halaman ini sebagai sumber daya http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
Catatan: __new__ selalu mengembalikan objek yang sesuai, jadi __init__ selalu dipanggil - bahkan jika instance sudah ada.
Oddthinking

10

Saya pikir jawaban sederhana untuk pertanyaan ini adalah bahwa, jika __new__mengembalikan nilai yang sama dengan kelas, __init__fungsi dijalankan, jika tidak maka tidak akan. Dalam hal ini, kode Anda kembali A._dict('key')dengan kelas yang sama cls, sehingga __init__akan dieksekusi.


10

Ketika __new__mengembalikan instance dari kelas yang sama, __init__dijalankan setelahnya pada objek yang dikembalikan. Yaitu Anda TIDAK bisa menggunakan __new__untuk mencegah __init__dijalankan. Bahkan jika Anda mengembalikan objek yang dibuat sebelumnya __new__, itu akan menjadi dua kali lipat (tiga kali lipat, dll ...) diinisialisasi __init__berulang kali.

Berikut ini adalah pendekatan umum untuk pola Singleton yang memperluas jawaban vartec di atas dan memperbaikinya:

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

Kisah lengkap ada di sini .

Pendekatan lain, yang sebenarnya melibatkan __new__adalah dengan menggunakan metode class:

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"

Harap perhatikan, bahwa dengan pendekatan ini Anda perlu menghias SEMUA metode Anda @classmethod, karena Anda tidak akan pernah menggunakan contoh nyata MyClass.


6

Mengacu pada dokumen ini :

Ketika mensubklasifikasikan tipe bawaan yang tidak dapat diubah seperti angka dan string, dan kadang-kadang dalam situasi lain, metode statis yang baru sangat berguna. baru adalah langkah pertama dalam konstruksi contoh, dipanggil sebelum init .

The baru metode ini disebut dengan kelas sebagai argumen pertama; tanggung jawabnya adalah mengembalikan contoh baru dari kelas itu.

Bandingkan ini dengan init : init dipanggil dengan instance sebagai argumen pertamanya, dan ia tidak mengembalikan apa pun; tanggung jawabnya adalah menginisialisasi instance.

Ada situasi di mana instance baru dibuat tanpa memanggil init (misalnya ketika instance diambil dari acar). Tidak ada cara untuk membuat instance baru tanpa memanggil yang baru (walaupun dalam beberapa kasus Anda bisa lolos dengan memanggil kelas dasar yang baru ).

Mengenai apa yang ingin Anda capai, ada juga info dokumen yang sama tentang pola Singleton

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

Anda juga dapat menggunakan implementasi ini dari PEP 318, menggunakan dekorator

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

1
Memanggil bentuk bastardized initdari __new__suara benar-benar aneh. Ini adalah untuk apa metaclasses.
Fisikawan Gila

6
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')

output:

NEW
INIT

NEW
INIT

EXISTS

NB Sebagai M._dictproperti efek samping secara otomatis dapat diakses dari Akarena A._dictitu berhati-hatilah untuk tidak menimpanya secara kebetulan.


Anda kehilangan __init__metode yang menetapkan cls._dict = {}. Anda mungkin tidak ingin kamus dibagikan oleh semua kelas dari metatype ini (tetapi +1 untuk ide).
Fisikawan Gila

5

__new__ harus mengembalikan instance kelas baru yang kosong. __init__ kemudian dipanggil untuk menginisialisasi instance itu. Anda tidak menelepon __init__ dalam "NEW" kasus __new__, jadi Anda dipanggil untuk itu. Kode yang memanggil __new__tidak melacak apakah __init__ telah dipanggil pada contoh tertentu atau tidak juga tidak seharusnya, karena Anda melakukan sesuatu yang sangat tidak biasa di sini.

Anda bisa menambahkan atribut ke objek dalam fungsi __init__ untuk menunjukkan bahwa itu telah diinisialisasi. Periksa keberadaan atribut itu sebagai hal pertama dalam __init__ dan jangan melanjutkan lebih jauh jika sudah.


5

Pembaruan untuk jawaban @AntonyHatchkins, Anda mungkin ingin kamus contoh yang terpisah untuk setiap kelas metatype, yang berarti bahwa Anda harus memiliki __init__metode dalam metaclass untuk menginisialisasi objek kelas Anda dengan kamus itu alih-alih menjadikannya global di semua kelas.

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()

Saya telah pergi ke depan dan memperbarui kode asli dengan __init__metode dan mengubah sintaks ke notasi Python 3 (no-arg panggilan ke superdan metaclass di argumen kelas bukan sebagai atribut).

Either way, poin penting di sini adalah bahwa inisialisasi kelas Anda ( __call__metode) tidak akan mengeksekusi baik __new__atau __init__jika kunci ditemukan. Ini jauh lebih bersih daripada menggunakan __new__, yang mengharuskan Anda untuk menandai objek jika Anda ingin melewati default__init__ langkah .


4

Menggali lebih dalam ke dalamnya!

Jenis kelas generik dalam CPython adalah typedan kelas dasarnya adalah Object(Kecuali jika Anda secara eksplisit mendefinisikan kelas basis lain seperti metaclass). Urutan panggilan tingkat rendah dapat ditemukan di sini . Metode pertama yang dipanggil adalah type_callyang kemudian memanggil tp_newdan kemudiantp_init .

Bagian yang menarik di sini adalah yang tp_newakan memanggil Objectmetode baru (kelas dasar) object_newyang melakukan a tp_alloc( PyType_GenericAlloc) yang mengalokasikan memori untuk objek :)

Pada titik itu objek dibuat dalam memori dan kemudian __init__metode dipanggil. Jika __init__tidak diimplementasikan di kelas Anda maka object_initdipanggil dan tidak melakukan apa-apa :)

Kemudian type_callkembalikan objek yang terikat ke variabel Anda.


4

Orang harus melihat __init__sebagai konstruktor sederhana dalam bahasa OO tradisional. Misalnya, jika Anda terbiasa dengan Java atau C ++, konstruktor meneruskan sebuah pointer ke instansnya sendiri secara implisit. Dalam kasus Jawa, itu adalah thisvariabel. Jika seseorang memeriksa kode byte yang dihasilkan untuk Java, orang akan melihat dua panggilan. Panggilan pertama adalah metode "baru", dan panggilan berikutnya adalah metode init (yang merupakan panggilan aktual ke konstruktor yang ditentukan pengguna). Proses dua langkah ini memungkinkan pembuatan instance aktual sebelum memanggil metode konstruktor dari kelas yang hanya metode lain dari instance itu.

Sekarang, dalam kasus Python, __new__adalah fasilitas tambahan yang dapat diakses oleh pengguna. Java tidak menyediakan fleksibilitas itu, karena sifatnya yang diketik. Jika suatu bahasa menyediakan fasilitas itu, maka implementor __new__dapat melakukan banyak hal dalam metode itu sebelum mengembalikan instance, termasuk membuat instance yang benar-benar baru dari objek yang tidak terkait dalam beberapa kasus. Dan, pendekatan ini juga bekerja dengan baik terutama untuk tipe yang tidak berubah dalam kasus Python.


4

Namun, saya agak bingung mengapa __init__selalu dipanggil setelah __new__.

Saya pikir analogi C ++ akan berguna di sini:

  1. __new__cukup mengalokasikan memori untuk objek. Variabel instance dari suatu objek membutuhkan memori untuk menahannya, dan inilah yang __new__akan dilakukan langkahnya .

  2. __init__ inisialisasi variabel internal objek ke nilai tertentu (bisa menjadi default).


2

The __init__disebut setelah__new__ sehingga ketika Anda menimpa di subclass, kode menambahkan Anda akan tetap dipanggil.

Jika Anda mencoba untuk subkelas kelas yang sudah memiliki __new__, seseorang yang tidak menyadari ini mungkin mulai dengan mengadaptasi __init__dan meneruskan panggilan ke bawah ke subkelas __init__. Konvensi panggilan __init__setelah ini__new__ membantu pekerjaan itu seperti yang diharapkan.

The __init__masih perlu untuk memungkinkan setiap parameter superclass __new__diperlukan, tetapi gagal untuk melakukannya biasanya akan membuat kesalahan runtime yang jelas. Dan __new__mungkin harus secara eksplisit mengizinkan *argsdan '** kw', untuk membuatnya jelas bahwa ekstensi itu OK.

Biasanya bentuk yang buruk memiliki keduanya __new__dan __init__di kelas yang sama pada tingkat warisan yang sama, karena perilaku yang digambarkan oleh poster aslinya.


1

Namun, saya agak bingung mengapa __init__selalu dipanggil setelah __new__.

Tidak banyak alasan selain itu hanya dilakukan seperti itu. __new__tidak memiliki tanggung jawab menginisialisasi kelas, beberapa metode lain tidak ( __call__, mungkin - saya tidak tahu pasti).

Saya tidak mengharapkan ini. Adakah yang bisa memberi tahu saya mengapa ini terjadi dan bagaimana saya menerapkan fungsi ini? (Selain menempatkan implementasi ke dalam __new__yang terasa cukup gila).

Anda bisa tidak __init__melakukan apa-apa jika sudah diinisialisasi, atau Anda bisa menulis metaclass baru dengan yang baru __call__yang memanggil __init__instance baru, dan sebaliknya hanya mengembalikan __new__(...).


1

Alasan sederhananya adalah bahwa yang baru digunakan untuk membuat instance, sedangkan init digunakan untuk menginisialisasi instance. Sebelum menginisialisasi, instance harus dibuat terlebih dahulu. Itu sebabnya baru harus dipanggil sebelum init .


1

Sekarang saya punya masalah yang sama, dan untuk beberapa alasan saya memutuskan untuk menghindari dekorator, pabrik, dan kacamata. Saya melakukannya seperti ini:

File utama

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

Kelas contoh

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

Digunakan

>>> 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>
  • Peringatan: Anda seharusnya tidak menginisialisasi Orang Tua, itu akan bertabrakan dengan kelas-kelas lain - kecuali jika Anda mendefinisikan cache terpisah di masing-masing anak, itu bukan yang kita inginkan.
  • Peringatan: Tampaknya kelas dengan Orang Tua sebagai kakek berperilaku aneh. [Tidak diverifikasi]

Cobalah online!

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.