Neden __init __ () her zaman __new __ () öğesinden sonra çağrılır?


568

Sadece sınıflarımdan birini düzene sokmaya çalışıyorum ve flyweight tasarım deseni ile aynı tarzda bazı işlevler getirdim .

Ancak, neden __init__hep çağrıldığım konusunda biraz kafam karıştı __new__. Bunu beklemiyordum. Biri bana bunun neden olduğunu ve bu işlevi başka türlü nasıl uygulayabileceğimi söyleyebilir mi? (Uygulamayı içine sokmak dışında __new__oldukça huzursuz hissettiriyor.)

İşte bir örnek:

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

Çıktılar:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

Neden?


tasarım desenini de anlamaya çalışıyordu ve ilk kez duyuldu: flyweight tasarım deseni .. ve neredeyse tüm popüler dillerde örnek olan çok iyi bir bağlantı.
EmmaYang

Yanıtlar:


598

__new__Yeni bir örnek oluşturmayı denetlemeniz gerektiğinde kullanın .

__init__Yeni bir örneğin başlatılmasını denetlemeniz gerektiğinde kullanın .

__new__örnek oluşturmanın ilk adımıdır. Önce denir ve sınıfınızın yeni bir örneğini döndürmekten sorumludur.

Buna karşılık, __init__hiçbir şey döndürmez; yalnızca oluşturulduktan sonra örneği başlatmaktan sorumludur.

Genel olarak, __new__str, int, unicode veya tuple gibi değişmez bir türü alt sınıflandırmadığınız sürece geçersiz kılmanız gerekmez .

Nisan 2008 yazı Gönderen: Ne zaman kullanılır __new__VS. __init__? mail.python.org adresinde.

Yapmaya çalıştığınız şeyin genellikle bir Fabrika ile yapıldığını ve bunu yapmanın en iyi yolunun bu olduğunu düşünmelisiniz . Kullanımı __new__iyi bir temiz çözüm değildir, bu yüzden lütfen bir fabrika kullanımını düşünün. Burada iyi bir fabrika örneğiniz var .


1
Sonunda __new__oldukça temiz hale gelen bir Fabrika sınıfının içinde kullanıldı , bu yüzden girişiniz için teşekkürler.
Dan

11
Maalesef, kullanımının __new__kesinlikle belirtilen vakalarla sınırlı olması gerektiğini kabul etmiyorum. Genişletilebilir genel sınıf fabrikaları uygulamak için çok yararlı buldum - Python sınıfları oluşturmak için yanlış kullanımı sorusuna cevabımı bakın ? bunun bir örneği. __new__
martineau

170

__new__statik sınıf yöntemidir, __init__örnek yöntemi ise. __new__önce örneği oluşturmanız gerekir, böylece __init__onu başlatabilir. Not __init__alır selfparametre olarak. Örnek oluşturuncaya kadarself .

Şimdi, Python'da singleton kalıbı uygulamaya çalıştığınızı topluyorum . Bunu yapmanın birkaç yolu var.

Ayrıca, Python 2.6'dan itibaren sınıf dekoratörleri kullanabilirsiniz .

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

@singleton
class MyClass:
  ...

8
@Tyler Uzun, "@singleton" un nasıl çalıştığını tam olarak anlamıyorum? Dekoratör bir işlev döndürdüğü için, ancak Sınıfım bir sınıftır.
Alcott

11
Neden sözlük? CL'ler her zaman aynı olacağından ve örneğin her bir tekil için yeni bir sözlük aldığınızdan, içinde yalnızca bir öğe bulunan bir sözlük oluşturuyorsunuz.
Winston Ewert

7
@Alcott: Hiçbir fikre gerek yok - dokümanlar size katılıyor.
Ethan Furman

2
@Alcott. evet dekoratör bir işlev döndürür. ancak hem sınıf hem de işlev çağrılabilir. Bence = {} örnekleri küresel bir değişken olmalıdır.
Tyler Long

4
@TylerLong Alcott'un iyi bir noktası var. Bu, adın MyClassorijinal sınıfın örneklerini döndüren bir işleve bağlı olmasına neden olur. Ama şimdi orijinal sınıfa hiç başvurmanın bir yolu yok ve MyClassbir işlev kırılıyor isinstance/ issubclasskontrol ediliyor , sınıf özniteliklerine / yöntemlerine doğrudan MyClass.something, superçağrılarda sınıfın adlandırılması , vb. uyguladığınız sınıf (ve programın geri kalanı).
Ben

148

En iyi bilinen OO dillerinde, gibi bir ifade SomeClass(arg1, arg2)yeni bir örnek atar, örneğin niteliklerini başlatır ve sonra döndürür.

En iyi bilinen OO dillerinde, "örneğin özniteliklerini başlat" bölümü , temel olarak yalnızca yeni örnek üzerinde çalışan bir kod bloğu olan bir kurucu tanımlanarak (kurucu ifadesine sağlanan argümanlar kullanılarak) her sınıf için özelleştirilebilir ) istediğiniz başlangıç ​​koşullarını ayarlamak için. Python'da bu, sınıfın __init__yöntemine karşılık gelir .

Python's __new__, "yeni bir örnek ayır" bölümünün sınıf başına benzer özelleştirmesinden başka bir şey değildir. Bu elbette yeni bir tane ayırmak yerine mevcut bir örneği döndürmek gibi olağandışı şeyler yapmanıza izin verir. Bu yüzden Python'da bu kısmı gerçekten de tahsisi içeren bir şey olarak düşünmemeliyiz; tek ihtiyacımız olan şey__new__ bir yerden uygun bir örnek bulmak.

Ama hala işin sadece yarısı ve Python sisteminin bazen işin diğer yarısını ( __init__) daha sonra çalıştırmak istediğini ve bazen de yapmadığını bilmenin bir yolu yok. Bu davranışı istiyorsanız, bunu açıkça söylemelisiniz.

Çoğu zaman, sadece ihtiyacınız __new__olan ya da ihtiyacınız olmayan __new__ya da __init__zaten başlatılmış bir nesnede farklı davranan şekilde yeniden düzenleme yapabilirsiniz . Eğer gerçekten, Python aslında izin vermez istiyorsanız böylece Ama, "İşi" yeniden tanımlama SomeClass(arg1, arg2)mutlaka çağrı gelmez __new__izledi __init__. Bunu yapmak için bir metasınıf oluşturmanız ve __call__yöntemini tanımlamanız gerekir .

Metasınıf sadece bir sınıfın sınıfıdır. Bir sınıf __call__yöntemi, sınıfın örneklerini çağırdığınızda ne olacağını kontrol eder. Yani bir metasınıf ' __call__yöntemi, bir sınıfı çağırdığınızda ne olacağını kontrol eder; yani , örnek oluşturma mekanizmasını baştan sona yeniden tanımlamanızı sağlar . Bu, singleton deseni gibi tamamen standart dışı bir örnek oluşturma işlemini en zarif şekilde uygulayabileceğiniz düzeydir. Aslında, kod 10'dan az hatları ile bir uygulayabilirsiniz Singletonsonra bile futz sizi gerektirmeyen metaclass __new__ hiç ve açabilirsiniz herhangi basitçe ekleyerek bir singleton içine aksi normalin sınıf __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

Ancak bu muhtemelen bu durum için gerçekten garanti edilenden daha derin bir sihirdir!


25

Alıntı belgelere :

Tipik uygulamalar, üst sınıfın __new __ () yöntemini "super (currentclass, cls) .__ new __ (cls [, ...])" kullanarak uygun argümanlarla çağırarak ve sonra yeni oluşturulan örneği gerektiği gibi değiştirerek sınıfın yeni bir örneğini oluşturur dönmeden önce.

...

__New __ () bir cls örneği döndürmezse, yeni örneğin __init __ () yöntemi çağrılmaz.

__new __ () temel olarak, değiştirilemeyen türlerin (int, str veya tuple gibi) alt sınıflarının örnek oluşturmayı özelleştirmesine izin vermeyi amaçlamaktadır.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Bu önemli bir nokta. Farklı bir örnek döndürürseniz, orijinal __init__asla çağrılmaz.
Jeffrey Jose

@tgray Cevabınızın dokümanlardan geldiğini anlıyorum, ancak cl'lerin bir örneğini döndürmemenin herhangi bir kullanım durumunu biliyorsanız merak ediyorum. Görünüşe göre döndürülen nesnesi sınıfı __new__için kontrol edildiğinde, yöntem başarısız denetimin sessizce geçmesine izin vermek yerine bir hata atar, çünkü neden sınıfın bir nesnesi dışında bir şey döndürmek istediğinizi anlamıyorum. .
soporific312

@ soporific312 Hiç kullanım vakası görmedim. Bu cevap , tasarımın gerekçelerinin bir kısmını tartışsa da, bu "özellik" ten yararlanan herhangi bir kod da görmediler.
tgray

12

Bu sorunun oldukça eski olduğunu fark ettim ama benzer bir sorunum vardı. Aşağıdakiler istediğimi yaptı:

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

Bu sayfayı kaynak olarak kullandım http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


7
Not: __new__ her zaman uygun bir nesne döndürür, bu nedenle __init__ her zaman çağrılır - örnek zaten mevcut olsa bile.
14'te

10

Bu sorunun basit cevabı __new__, sınıfla aynı türde bir değer döndürürse, __init__işlev yürütülür, aksi takdirde olmaz olduğunu düşünüyorum. Bu durumda, kodunuz A._dict('key')aynı sınıf olan döndürülür cls, böylece __init__yürütülür.


10

Ne zaman __new__aynı sınıfın döner örneği, __init__döndürülen nesne üzerinde sonradan çalışır. Yani çalıştırılmasını __new__önlemek __init__için KULLANAMAZSINIZ. Önceden oluşturulmuş bir nesneyi __new__döndürseniz bile, __init__tekrar tekrar iki kez başlatılır (üçlü, vb.) .

Yukarıda vartec cevabını genişleten ve düzelten Singleton desenine genel yaklaşım:

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

Tüm hikaye burada .

Aslında içeren başka bir yaklaşım sınıf __new__yöntemlerini kullanmaktır:

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"

Lütfen bu yaklaşımla TÜM yöntemlerinizi dekore etmeniz gerektiğine dikkat edin @classmethod, çünkü hiçbir zaman gerçek bir örneğini kullanmayacaksınız MyClass.


6

Bu dokümana referansla :

Sayılar ve dizgiler gibi değişmez yerleşik türleri alt sınıflara ayırırken ve bazen başka durumlarda, yeni statik yöntem kullanışlı olur. Yeni öncesinde çağrılan örneği yapımında ilk adım olduğunu init .

Yeni yöntem, ilk bağımsız değişken olarak sınıf olarak adlandırılır; sorumluluğu o sınıfın yeni bir örneğini iade etmektir.

Bunu init ile karşılaştırın : init ilk argümanı olarak bir örnekle çağrılır ve hiçbir şey döndürmez; onun sorumluluğu örneği başlatmaktır.

İnit çağrılmadan yeni bir örneğin oluşturulduğu durumlar vardır (örneğin, örnek bir turşudan yüklendiğinde). Yeni çağırmadan yeni bir örnek oluşturmanın bir yolu yoktur (ancak bazı durumlarda bir temel sınıfın yeni çağrısından kaçabilirsiniz ).

Neyi başarmak istediğinizle ilgili olarak, aynı dokümanda Singleton modeli hakkında da

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

bu uygulamayı bir dekoratör kullanarak PEP 318'den de kullanabilirsiniz

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

@singleton
class MyClass:
...

1
Bir bastardized formunu çağırma initgelen __new__sesler gerçekten Hacky. Metasınıflar bunun için.
Mad Physicist

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

çıktılar:

NEW
INIT

NEW
INIT

EXISTS

Not: Bir yan etki M._dictözelliğine otomatik olarak erişilebilir hale gelir A, A._dictbu nedenle yanlışlıkla üzerine yazmamaya dikkat edin.


Ayarlayan bir __init__yöntem eksik cls._dict = {}. Muhtemelen bu metatipin tüm sınıfları tarafından paylaşılan bir sözlük istemezsiniz (ancak fikir için +1).
Mad Physicist

5

__new__ yeni, boş bir sınıf örneği döndürmelidir. __init__ daha sonra bu örneği başlatmak için çağrılır. __New__'nin "YENİ" durumunda __init__'yi aramıyorsunuz, bu yüzden sizin için çağrılıyor. Çağıran kod, __new____init__'nin belirli bir örnekte çağrılıp çağrılmadığını izlemez, burada çok sıradışı bir şey yapıyorsunuz.

__İnit__ işlevindeki nesneye, başlatıldığını belirtmek için bir öznitelik ekleyebilirsiniz. __İnit__ içindeki ilk şey olarak bu özelliğin varlığını kontrol edin ve eğer varsa daha fazla ilerlemeyin.


5

@AntonyHatchkins yanıtında yapılan bir güncellemede, büyük olasılıkla metatipin her sınıfı için ayrı bir örnek sözlüğü istersiniz, yani __init__sınıf nesnenizi tüm sınıflarda global yapmak yerine sözlüğüyle başlatmak için metasınıfta bir yönteminizin olması gerekir .

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

Ben devam etti ve bir __init__yöntemle orijinal kodu güncelledim ve sözdizimini Python 3 gösterimi (no-arg callsuper ve öznitelik yerine sınıf argümanlarında metaclass) değiştirdi.

Her iki durumda da, burada önemli olan nokta, sınıf başlatıcısının ( __call__yöntem) ya __new__da __init__anahtarın bulunmaması durumunda yürütülmemesidir . Bu, __new__varsayılan __init__adımı atlamak istiyorsanız nesneyi işaretlemenizi gerektiren, kullanmaktan çok daha temizdir .


4

Biraz daha derine inmek!

CPython'da genel bir sınıf türü typeve temel sınıfı şudur Object(Açıkça bir metasınıf gibi başka bir temel sınıf tanımlamazsanız). Düşük seviye çağrıların sırasını burada bulabilirsiniz . Denilen ilk yöntem, type_calldaha sonra tp_newve sonra çağıran yöntemdir tp_init.

Buradaki ilginç kısım , nesnenin hafızasını tahsis eden bir ( ) yapan 's (base class) yeni yöntemini tp_newçağırmasıdır :)Objectobject_newtp_allocPyType_GenericAlloc

Bu noktada nesne bellekte oluşturulur ve sonra __init__yöntem çağrılır. Eğer __init__sınıfınızda uygulanmazsa o object_initzaman çağrılır ve hiçbir şey yapmaz :)

Sonra type_callsadece değişkeninize bağlanan nesneyi döndürür.


4

__init__Geleneksel OO dillerinde basit bir kurucu olarak bakılmalıdır . Örneğin, Java veya C ++ hakkında bilginiz varsa, kurucuya örtülü olarak kendi örneğine bir işaretçi geçirilir. Java durumunda, bu thisdeğişkendir. Java için oluşturulan bayt kodunu inceleyecek olsaydı, iki çağrı fark edilirdi. İlk çağrı bir "yeni" yönteme, ardından bir sonraki çağrı init yöntemine (kullanıcı tanımlı kurucuya gerçek çağrıdır). Bu iki aşamalı işlem, o örneğin başka bir yöntemi olan sınıfın yapıcı yöntemini çağırmadan önce gerçek örneğin oluşturulmasını sağlar.

Şimdi, Python durumunda __new__, kullanıcı tarafından erişilebilen ek bir tesis. Java, türünün niteliği nedeniyle bu esnekliği sağlamaz. Bir dil bu olanağı sağlamışsa, uygulayıcısı __new__, örneği döndürmeden önce, bazı durumlarda ilişkisiz bir nesnenin tamamen yeni bir örneğini oluşturmak da dahil olmak üzere, bu yöntemde birçok şey yapabilir. Ve bu yaklaşım, özellikle Python durumunda değişmez tipler için de işe yarar.


4

Ancak, neden __init__hep çağrıldığım konusunda biraz kafam karıştı __new__.

C ++ benzetmesinin burada yararlı olacağını düşünüyorum:

  1. __new__nesne için bellek ayırır. Bir nesnenin örnek değişkenleri, onu tutmak için belleğe ihtiyaç duyar ve bu adımın __new__yapacağı şeydir .

  2. __init__ nesnenin dahili değişkenlerini belirli değerlere sıfırlar (varsayılan olabilir).


2

__init__Sonra denir__new__ , bir alt sınıfta geçersiz olduğunda, sizin ilave kod hala denilen alacak şekilde yerleştirin.

Zaten bir sınıfı olan bir sınıfı alt sınıflamaya çalışıyorsanız, bunun __new__farkında olmayan biri __init__çağrıyı uyarlayarak ve alt sınıfa yönlendirerek başlayabilir __init__. Bu toplantı __init__sonrası__new__ beklendiği gibi bu iş olur.

Yine __init__de, üst sınıfın __new__gereken herhangi bir parametreye izin vermesi gerekir, ancak bunu yapmamak genellikle açık bir çalışma zamanı hatası oluşturur. Ve __new__muhtemelen *argsuzantının uygun olduğunu açıkça belirtmek için açıkça ve '** kw' izin vermelidir .

Orijinal posterin tarif ettiği davranış nedeniyle, hem __new__ve __init__aynı sınıfta aynı kalıtım seviyesinde olmak genellikle kötü bir formdur .


1

Ancak, neden __init__hep çağrıldığım konusunda biraz kafam karıştı __new__.

Bunun dışında pek bir neden yok, bu şekilde yapılır. __new__Sınıfı başlatma sorumluluğuna sahip değildir, başka bir yöntem de yapar ( __call__muhtemelen-- emin değilim).

Bunu beklemiyordum. Biri bana bunun neden olduğunu ve bu işlevi başka türlü nasıl uyguladığımı söyleyebilir mi? (uygulamayı içine yerleştirmek dışında __new__oldukça hileli hissediyorum).

Sen olabilir __init__zaten bir ilk Girilmişse do şey, veya yeni ile yeni metaclass yazabilirsiniz __call__sadece çağrılar __init__yeni örneklerinde ve aksi takdirde sadece getiri __new__(...).


1

Bunun basit nedeni, yeni öğenin örnek oluşturmak için kullanılması, init ise örnek başlatmak için kullanılmasıdır. Başlatmadan önce önce örnek oluşturulmalıdır. Bu yüzden init önce yeni çağrılmalıdır .


1

Şimdi aynı problemim var ve bazı nedenlerden dolayı dekoratörler, fabrikalar ve metasınıflardan kaçınmaya karar verdim. Ben böyle yaptım:

Ana dosya

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

Örnek sınıflar

class A(Parent):

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


class B(Parent):

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

Kullanımda

>>> 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>
  • Uyarı: Sen initialize değil Veli, bu olmalıdır olacak diğer sınıfları ile çarpışır - Eğer istediğimiz değil ne olduğunu, çocukların her birinde ayrı önbellek tanımlanmadığı sürece.
  • Uyarı: Büyük ebeveynin garip davrandığı gibi Ebeveynli bir sınıf gibi görünüyor. [Doğrulanmamış]

Çevrimiçi deneyin!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.