Vraag Hoe moet ik standaardwaarden voor bijvoorbeeld variabelen in Python declareren?


Moet ik mijn klas standaardwaarden als deze geven:

class Foo:
    num = 1

of vind je dit leuk?

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

In deze vraag Ik ontdekte dat in beide gevallen

bar = Foo()
bar.num += 1

is een goed gedefinieerde operatie.

Ik begrijp dat de eerste methode me een klassenvariabele zal geven terwijl de tweede methode dat niet zal doen. Als ik echter geen klassenvariabele nodig heb, maar alleen een standaardwaarde voor mijn instantievariabelen moet instellen, zijn beide methoden even goed? Of een van hen meer 'pythonisch' dan de andere?

Een ding dat ik heb opgemerkt, is dat in de Django-tutorial ze gebruiken de tweede methode om modellen te declareren. Persoonlijk denk ik dat de tweede methode eleganter is, maar ik zou graag willen weten wat de 'standaard' manier is.


61
2018-04-21 08:11


oorsprong


antwoorden:


Uitbreiding van het antwoord van bp, ik wilde je laten zien wat hij bedoelde met onveranderlijke typen.

Ten eerste is dit goed:

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

Dit werkt echter alleen voor onveranderlijke (niet-veranderbare) typen. Als de standaardwaarde veranderbaar was (wat betekent dat deze kan worden vervangen), zou dit in de plaats komen:

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

Opmerking zowel a als b hebben een gedeeld kenmerk. Dit is vaak ongewenst.

Dit is de Pythonische manier om standaardwaarden te definiëren voor bijvoorbeeld variabelen, wanneer het type veranderbaar is:

>>> 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
[]

De reden waarom mijn eerste codefragment werkt is omdat, met onveranderlijke typen, Python er een nieuw exemplaar van maakt wanneer je maar wilt. Als je 1 op 1 moet toevoegen, maakt Python een nieuwe 2 voor je, omdat de oude 1 niet kan worden gewijzigd. De reden is vooral voor hashing, geloof ik.


94
2018-04-21 09:02



De twee fragmenten doen verschillende dingen, dus het is geen kwestie van smaak, maar een kwestie van wat het juiste gedrag is in uw context. Python-documentatie verklaart het verschil, maar hier zijn enkele voorbeelden:

Tentoonstelling A

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

Dit bindt num naar de Foo instanties. Wijziging in dit veld wordt niet doorgegeven aan andere instanties.

Dus:

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

Bijlage B

class Bar:
  num = 1

Dit bindt num naar de bar klasse. Veranderingen worden gepropageerd!

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

Werkelijk antwoord

Als ik geen klassenvariabele nodig heb, maar alleen een standaardwaarde voor mijn instantievariabelen moet instellen, zijn beide methoden even goed? Of een van hen meer 'pythonisch' dan de andere?

De code in exhibit B is hier fout in: waarom zou je een class-attribuut (standaardwaarde bij het maken van een instance) aan de single-instance willen binden?

De code in tentoonstelling A is goed.

Als u bijvoorbeeld standaardwaarden in uw constructor wilt opgeven, zou ik dit doen:

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

...of zelfs:

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

... of zelfs: (liever, maar als en alleen als u te maken hebt met onveranderlijke typen!)

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

Op deze manier kunt u doen:

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

40
2018-04-21 08:20



Het gebruik van klasleden voor standaardwaarden van instantievariabelen is geen goed idee, en het is de eerste keer dat ik dit idee al heb genoemd. Het werkt in uw voorbeeld, maar in veel gevallen kan het mislukken. Als de waarde bijvoorbeeld veranderbaar is, zal mutatie op een ongewijzigd exemplaar de standaardinstelling wijzigen:

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

3
2018-04-21 08:24



Het gebruik van klassen om standaardwaarden te geven, werkt heel goed, zolang je maar voorzichtig bent om het te doen met onveranderlijke waarden. Als je het probeert te doen met een lijst of een dictaat dat behoorlijk dodelijk zou zijn. Het werkt ook waar het instantiekenmerk een verwijzing naar een klasse is, zolang de standaardwaarde None is.

Ik heb gezien dat deze techniek zeer succesvol is gebruikt bij het repeteren, wat een raamwerk is dat bovenop Zope loopt. Het voordeel hiervan is niet alleen dat wanneer uw klasse in de database blijft bestaan, alleen de niet-standaardkenmerken moeten worden opgeslagen, maar ook wanneer u een nieuw veld in het schema moet toevoegen, zien alle bestaande objecten het nieuwe veld met de standaardwaarde waarde zonder de noodzaak om de opgeslagen gegevens daadwerkelijk te wijzigen.

Ik vind het ook goed werkt in meer algemene codering, maar het is een stijl ding. Gebruik waar je het gelukkigst mee bent.


3
2018-04-21 08:35



U kunt ook klassevariabelen declareren als Geen, waardoor propagatie wordt voorkomen. Dit is handig als u een goed gedefinieerde klasse nodig hebt en AttributeErrors wilt voorkomen. Bijvoorbeeld:

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

Ook als je standaardinstellingen nodig hebt:

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

Volg natuurlijk de informatie in de andere antwoorden over het gebruik van veranderlijke vs onveranderlijke typen als de standaard in __init__.


0
2018-05-28 19:07