Python (ve Python C API): __new__ ve __init__


126

Sormak üzere olduğum soru Python'un __new__ ve __init__ kullanımının bir kopyası gibi görünüyor ? ama ne olursa olsun, __new__ve arasındaki pratik farkın tam olarak ne olduğu benim için hala belirsiz __init__.

Bunun __new__nesneler yaratmak için olduğunu ve __init__nesneleri başlatmak için olduğunu söylemeden önce , açık konuşayım: Anlıyorum. Aslında, bu ayrım benim için oldukça doğal, çünkü yeni yerleşimin olduğu C ++ deneyimim var , bu da benzer şekilde nesne tahsisini başlatmadan ayırıyor.

Python C API öğretici şöyle açıklıyor:

Yeni üye, türdeki nesneleri oluşturmaktan (başlatmanın aksine) sorumludur. __new__()Yöntem olarak Python'da ortaya çıkar . ... Yeni bir yöntemi uygulamanın bir nedeni, örnek değişkenlerinin başlangıç ​​değerlerini sağlamaktır .

Yani, evet - ben almak Ne __new__yapar, ama buna rağmen, ben hala onu Python yararlı neden anlamıyorum. Verilen örnek __new__, "örnek değişkenlerinin başlangıç ​​değerlerini garanti altına almak" istiyorsanız bunun yararlı olabileceğini söylüyor . Peki, bu tam olarak ne __init__yapacak değil mi?

C API eğitiminde, yeni bir Type ("Noddy" olarak adlandırılır) oluşturulduğu ve Type'ın __new__işlevinin tanımlandığı bir örnek gösterilmektedir . Noddy türü, adı verilen bir dize üyesi içerir firstve bu dize üyesi, aşağıdaki gibi boş bir dizeyle başlatılır:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    .....

    self->first = PyString_FromString("");
    if (self->first == NULL)
    {
       Py_DECREF(self);
       return NULL;
    }

    .....
}

__new__Burada tanımlanan yöntem olmadan, PyType_GenericNewtüm örnek değişken üyelerini NULL olarak başlatan kullanmamız gerektiğine dikkat edin. Dolayısıyla, __new__yöntemin tek yararı , örnek değişkeninin NULL yerine boş bir dize olarak başlayacak olmasıdır. Ama bu neden her zaman yararlıdır, çünkü örnek değişkenlerimizin bir varsayılan değerle başlatıldığından emin olsaydık, bunu __init__yöntemde yapabilirdik?

Yanıtlar:


137

Fark esas olarak değişken ve değişmez türlerde ortaya çıkar.

__new__bir türü ilk bağımsız değişken olarak kabul eder ve (genellikle) bu türün yeni bir örneğini döndürür. Bu nedenle hem değişken hem de değişmez türlerle kullanım için uygundur.

__init__bir örneği ilk argüman olarak kabul eder ve bu örneğin niteliklerini değiştirir. Bu, oluşturulduktan sonra arama yoluyla değiştirilmesine izin vereceğinden, değişmez bir tür için uygun değildir obj.__init__(*args).

Davranışını karşılaştırın tupleve list:

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

Neden ayrı olduklarına gelince (basit tarihsel nedenler dışında): __new__yöntemler, doğruya ulaşmak için bir sürü standart şablon gerektirir (ilk nesne oluşturma ve ardından nesneyi en sonunda döndürmeyi hatırlama). __init__yöntemler, aksine, ayarlamanız gereken nitelikleri belirlediğiniz için son derece basittir.

__init__Yöntemlerin yazılması daha kolay olmasının ve yukarıda belirtilen değişken ve değişmez ayrımının yanı sıra , ayırma, __init__içinde kesinlikle gerekli olan örnek değişmezlerini ayarlayarak alt sınıflardaki üst sınıfın çağrılmasını isteğe bağlı hale getirmek için de kullanılabilir __new__. Yine de bu genellikle şüpheli bir uygulamadır - genellikle __init__gerektiğinde ebeveyn sınıf yöntemlerini çağırmak daha açıktır .


1
"ortak metin" olarak bahsettiğiniz kod standart __new__değildir, çünkü ortak metin asla değişmez. Bazen bu belirli kodu farklı bir şeyle değiştirmeniz gerekir.
Miles Rout

13
Örneği oluşturmak veya başka bir şekilde edinmek (genellikle bir superçağrı ile) ve örneği geri döndürmek, herhangi bir __new__uygulamanın ve bahsettiğim "standart şablonun" gerekli parçalarıdır . Aksine, passgeçerli bir uygulamadır __init__- herhangi bir gerekli davranış yoktur.
ncoghlan

37

Muhtemelen başka kullanımları da vardır, __new__ancak gerçekten açık olan bir tane var: Kullanmadan değişmez bir türü alt sınıflara alamazsınız __new__. Örneğin, yalnızca 0 ile arasındaki integral değerleri içerebilen bir demet alt sınıfı oluşturmak istediğinizi varsayalım size.

class ModularTuple(tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

Bunu basitçe şununla __init__yapamazsınız - selfiçinde değişiklik yapmaya çalışırsanız __init__, yorumlayıcı değişmez bir nesneyi değiştirmeye çalıştığınızdan şikayet ederdi.


1
Neden süper kullanmalıyız anlamıyorum? Demek istediğim, new neden süper sınıfın bir örneğini döndürmeli? Senin dediği gibi Dahası, neden biz cls için açıkça geçmelidir yeni ? super (ModularTuple, cls) bağlı bir yöntem döndürmüyor mu?
Alcott

3
@Alcott, bence davranışını yanlış anlıyorsunuz __new__. Biz geçmesi clsiçin açıkça __new__okuyabilir olarak Çünkü burada __new__ her zaman ilk argüman olarak türünü gerektiriyor. Daha sonra bu türün bir örneğini döndürür. Dolayısıyla, süper sınıfın bir örneğini geri vermeyeceğiz - bir örneğini geri veriyoruz cls. Bu durumda, söylemiş olduğumuz gibi tuple.__new__(ModularTuple, tup).
senderle

35

__new__()bağlı olduğu sınıf dışındaki türlerdeki nesneleri döndürebilir. __init__()yalnızca sınıfın mevcut bir örneğini başlatır.

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5

Bu şimdiye kadarki en yalın açıklama.
Tarık

Pek doğru değil. __init__Benzeri görünen kod içeren yöntemlerim var self.__class__ = type(...). Bu, nesnenin yarattığınızı düşündüğünüzden farklı bir sınıfta olmasına neden olur. Aslında bunu intsizin yaptığınız gibi değiştiremem ... Yığın türleri veya başka bir şey hakkında bir hata alıyorum ... ama onu dinamik olarak oluşturulmuş bir sınıfa atama örneğim işe yarıyor.
ArtOfWarfare

Ben de ne zaman __init__()çağrıldığı konusunda kafam karıştı . Örneğin, lonetwin'in cevabında , hangi türün döndüğüne bağlı olarak ya ya Triangle.__init__()da Square.__init__()otomatik olarak çağrılır __new__(). Eğer cevap söylemek (ve bu başka bir yerde okudum) kadarıyla, çünkü olması gerektiği ikisine gibi görünmüyor itibaren Shape.__new__() değil bir örneğini dönencls (ne de onun bir alt sınıfının bir).
martineau

1
@martineau: __init__()Bireysel nesneler örneği olsun zaman lonetwin yanıtında yöntemler denilen olsun (yani zaman onların __new__() metot bize) değil, Shape.__new__()geri döner.
Ignacio Vazquez-Abrams

Ahh, doğru, Shape.__init__()(olsaydı) aranmazdı. Şimdi her şey daha mantıklı ...:¬)
martineau

13

Tam bir cevap değil ama belki de farkı gösteren bir şey.

__new__bir nesnenin oluşturulması gerektiğinde her zaman çağrılacaktır. Aranmayacağı bazı durumlar vardır __init__. Bir örnek, bir turşu dosyasından nesneleri çıkardığınızda, bunlar tahsis edilecek ( __new__) ancak başlatılmayacak ( __init__).


Ben çağrı misiniz init gelen yeni ben bellek tahsis edilecek ve veri başlatılması isteseydi? Örnek init oluşturulurken yeni yoksa neden çağrılır?
redpix_

2
__new__Yöntemin görevi , sınıfın bir örneğini yaratmak (bu bellek ayırmayı ifade eder) ve onu döndürmektir. Başlatma ayrı bir adımdır ve genellikle kullanıcı tarafından görülebilen şeydir. Karşılaştığınız belirli bir sorun varsa lütfen ayrı bir soru sorun.
Noufal Ibrahim

3

Sadece tanımlamaya karşı tanımlamanın amacı (davranışın aksine) hakkında bir kelime eklemek istiyorum .__new____init__

Bir sınıf fabrikasını tanımlamanın en iyi yolunu anlamaya çalışırken (diğerlerinin yanı sıra) bu soruyla karşılaştım. __new__Kavramsal olarak farklı olan yollardan birinin __init__faydasının __new__tam olarak soruda ifade edilen şey olduğunu fark ettim :

Bu nedenle __new__ yönteminin tek yararı, örnek değişkeninin NULL yerine boş bir dize olarak başlayacak olmasıdır. Ama bu neden her zaman yararlıdır, çünkü örnek değişkenlerimizin bir varsayılan değerle başlatıldığından emin olsaydık, bunu __init__ yönteminde yapabilirdik?

Belirtilen senaryoyu göz önünde bulundurarak, örnek gerçekte bir sınıf olduğunda, örnek değişkenlerinin başlangıç ​​değerlerini önemsiyoruz . Dolayısıyla, çalışma zamanında dinamik olarak bir sınıf nesnesi oluşturuyorsak ve bu sınıfın yaratılan sonraki örnekleri hakkında özel bir şey tanımlamamız / kontrol etmemiz gerekirse, bu koşulları / özellikleri __new__bir meta sınıf yönteminde tanımlarız .

Bu konuda kafam karıştı, ta ki kavramın anlamını değil, uygulamasını gerçekten düşünene kadar. İşte farkı netleştireceğini umduğumuz bir örnek:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

Bunun sadece şeytani bir örnek olduğunu unutmayın. Yukarıdaki gibi bir sınıf fabrikası yaklaşımına başvurmadan bir çözüm elde etmenin birden fazla yolu vardır ve çözümü bu şekilde uygulamaya koymayı seçsek bile, kısalık uğruna dışarıda bırakılan küçük uyarılar vardır (örneğin, metasınıf açıkça beyan etmek) )

Normal bir sınıf oluşturuyorsanız (diğer bir deyişle __new__, metasınıf olmayan bir sınıf), ncoghlan'ın yanıt cevabındaki değişken ve değişmez senaryo gibi özel bir durum olmadığı sürece gerçekten mantıklı değildir (bu aslında tanımlama kavramının daha spesifik bir örneğidir) yaratılan sınıfın / türün ilk değerleri / özellikleri, __new__daha sonra aracılığıyla başlatılacak __init__).

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.