Python'da örnek değişkenleri için varsayılan değerleri nasıl bildirmeliyim?


91

Sınıf üyelerime aşağıdaki gibi varsayılan değerler vermeli miyim:

class Foo:
    num = 1

veya bunun gibi mi?

class Foo:
    def __init__(self):
        self.num = 1

Gelen bu soruya ben, her iki durumda da keşfetti

bar = Foo()
bar.num += 1

iyi tanımlanmış bir işlemdir.

İlk yöntemin bana bir sınıf değişkeni vereceğini, ikincisinin vermeyeceğini anlıyorum. Ancak, bir sınıf değişkenine ihtiyacım yoksa, ancak yalnızca örnek değişkenlerim için varsayılan bir değer ayarlamam gerekiyorsa, her iki yöntem de eşit derecede iyi mi? Veya biri diğerinden daha fazla 'pitonik' mi?

Fark ettiğim bir şey, Django eğitiminde Modelleri bildirmek için ikinci yöntemi kullandıklarıdır. Şahsen ben ikinci yöntemin daha zarif olduğunu düşünüyorum, ancak 'standart' yolun ne olduğunu bilmek istiyorum.

Yanıtlar:


135

BP'nin cevabını genişleterek, size onun değişmez tiplerle ne demek istediğini göstermek istedim.

Birincisi, sorun değil:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

Ancak, bu yalnızca değişmez (değiştirilemez) türler için işe yarar. Varsayılan değer değiştirilebilir olsaydı (değiştirilebilir anlamına gelir), bunun yerine şu olur:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Hem o Not ave bpaylaşılan bir niteliği vardır. Bu genellikle istenmeyen bir durumdur.

Bu, tür değiştirilebilir olduğunda örnek değişkenler için varsayılan değerleri tanımlamanın Pythonic yoludur:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

İlk kod parçacığımın çalışmasının nedeni, değişmez türlerde Python'un istediğiniz zaman yeni bir örnek oluşturmasıdır. 1'e 1 eklemeniz gerekiyorsa, Python sizin için yeni bir 2 oluşturur çünkü eski 1 değiştirilemez. Sebebin çoğunlukla hashing olduğuna inanıyorum.


2
Son zamanlarda, değiştirilebilir öznitelikler için varsayılan değerleri uygulamanın çok daha basit bir yoluna rastladım. Sadece yöntem self.attr = attr or []dahilinde yazın __init__. Aynı sonucu elde ediyor (sanırım) ve hala net ve okunabilir.
Bill

5
Üzgünüm millet. Yukarıdaki yorumumdaki öneri üzerine biraz daha araştırma yaptım ve görünüşe göre tamamen aynı değil ve tavsiye edilmiyor .
Bill

TestC sınıfı neden boş parantezlere sahip? Bu bir Python 2 mirası mı?
ChrisG

55

İki parça farklı şeyler yapar, bu yüzden bu bir zevk meselesi değil, bağlamınızda doğru davranışın ne olduğu meselesidir. Python belgeleri farkı açıklar, ancak işte bazı örnekler:

sergi A

class Foo:
  def __init__(self):
    self.num = 1

Bu num, Foo örneklerine bağlanır . Bu alanda yapılan değişiklik diğer örneklere yayılmaz.

Böylece:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Sergi B

class Bar:
  num = 1

Bu num, Bar sınıfına bağlanır . Değişiklikler yayılır!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Gerçek cevap

Bir sınıf değişkenine ihtiyacım yoksa, ancak yalnızca örnek değişkenlerim için varsayılan bir değer ayarlamam gerekiyorsa, her iki yöntem de eşit derecede iyi mi? Yoksa biri diğerinden daha fazla 'pitonik' mi?

Gösterim B'deki kod bunun için tamamen yanlıştır: neden bir sınıf özniteliğini (örnek oluşturmada varsayılan değer) tek bir örneğe bağlamak isteyesiniz?

Ek A'daki kod tamam.

Yapıcınızdaki örnek değişkenleri için varsayılanlar vermek isterseniz, bunu yapacağım:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...ya da:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

... veya hatta: (tercih edilir, ancak ancak ve ancak değişmez türlerle uğraşıyorsanız!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

Bu şekilde şunları yapabilirsiniz:

foo1 = Foo(4)
foo2 = Foo() #use default

1
Bu örnekte kullanılan başlatma yöntemi, başlatma için alt çizgi___init__underscore'dan farklı mı @badp
Eliethesaiyan

Değil ilk örnek olmalıdef __init__(self, num = None)
PyWalker2797

6

Varsayılan değerler vermek için sınıf üyelerini kullanmak, yalnızca bunu değişmez değerlerle yapmaya dikkat ettiğiniz sürece çok iyi çalışır. Bunu bir liste ya da sözle yapmaya çalışırsanız, bu oldukça ölümcül olur. Ayrıca, varsayılan değer Yok olduğu sürece, örnek niteliğinin bir sınıfa başvuru olduğu durumlarda da çalışır.

Zope üzerinde çalışan bir çerçeve olan repoze işleminde bu tekniğin çok başarılı bir şekilde kullanıldığını gördüm. Buradaki avantaj, yalnızca sınıfınız veritabanında kaldığında yalnızca varsayılan olmayan özniteliklerin kaydedilmesi gerekmemesi değil, aynı zamanda şemaya yeni bir alan eklemeniz gerektiğinde mevcut tüm nesnelerin varsayılanı ile yeni alanı görmesidir. saklanan verileri gerçekten değiştirmeye gerek kalmadan değer.

Daha genel kodlamada da işe yaradığını düşünüyorum, ancak bu bir stil meselesi. En mutlu olduğunuz şeyi kullanın.


3

Örnek değişkenlerinin varsayılan değerleri için sınıf üyelerini kullanmak iyi bir fikir değildir ve bu fikrin bahsettiğini ilk kez görüyorum. Örneğinizde işe yarıyor, ancak birçok durumda başarısız olabilir. Örneğin, değer değiştirilebilirse, değiştirilmemiş bir örnekte değiştirilmesi varsayılanı değiştirir:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]

sadece []oralarda ilk klonlanmış olanlar dışında ; x.lve y.lhem kötü isimler hem de aynı referansa bağlanan farklı anlık değerlerdir. (Dil avukatı terimleri
uyduruldu

1

Ayrıca, sınıf değişkenlerini Yok olarak bildirebilir, bu da yayılmayı engeller. Bu, iyi tanımlanmış bir sınıfa ihtiyacınız olduğunda ve AttributeErrors'ı önlemek istediğinizde kullanışlıdır. Örneğin:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Ayrıca varsayılanlara ihtiyacınız varsa:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Elbette, değişken ve değişmez türlerin varsayılan olarak kullanılmasıyla ilgili diğer yanıtlardaki bilgileri takip etmeye devam edin __init__.


0

İle dataclasses , Python 3.7 eklenen bir özellik, şimdi sınıf örneklerinde varsayılan değerler ayarlayarak elde etmek henüz başka (oldukça elverişli) yolu yoktur. Dekoratör dataclass, sınıfınızda yapıcı gibi birkaç yöntemi otomatik olarak oluşturacaktır. Yukarıda bağlantılı dokümantasyonun belirttiği gibi, "Bu oluşturulan yöntemlerde kullanılacak üye değişkenler, PEP 526 tipi açıklamalar kullanılarak tanımlanır ".

OP'nin örneğini düşünürsek, bunu şu şekilde uygulayabiliriz:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

Bu sınıfın türünde bir nesne oluştururken isteğe bağlı olarak değerin üzerine yazabiliriz.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
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.