Hoe vermijd ik het “self.x = x; self.y = y; self.z = z” patroon in __init__?

Ik zie patronen zoals

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...

vrij vaak, vaak met veel meer parameters. Is er een goede manier om dit soort vervelende herhalingen te voorkomen? Moet de klas in plaats daarvan erven van namedtuple?


Antwoord 1, autoriteit 100%

Disclaimer:het lijkt erop dat verschillende mensen zich zorgen maken over het presenteren van deze oplossing, dus ik zal een zeer duidelijke disclaimer geven. U mag deze oplossing niet gebruiken. Ik geef het alleen ter informatie, dus je weet dat de taal hiertoe in staat is. De rest van het antwoord toont alleen taalmogelijkheden, niet het gebruik ervan op deze manier.


Er is niet echt iets mis met het expliciet kopiëren van parameters naar attributen. Als je te veel parameters in de ctor hebt, wordt het soms als een codegeur beschouwd en misschien moet je deze parameters in minder objecten groeperen. Andere keren is het nodig en is er niets mis mee. Hoe dan ook, expliciet doen is de beste keuze.

Omdat u zich echter afvraagt ​​HOE het kan (en niet of het moet), is de volgende oplossing:

class A:
    def __init__(self, **kwargs):
        for key in kwargs:
          setattr(self, key, kwargs[key])
a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2

Antwoord 2, autoriteit 81%

Bewerken:
Als je python 3.7+ hebt, gebruik dan gewoon dataclasses

Een decorateuroplossing die de handtekening behoudt:

import decorator
import inspect
import sys
@decorator.decorator
def simple_init(func, self, *args, **kws):
    """
    @simple_init
    def __init__(self,a,b,...,z)
        dosomething()
    behaves like
    def __init__(self,a,b,...,z)
        self.a = a
        self.b = b
        ...
        self.z = z
        dosomething()
    """
    #init_argumentnames_without_self = ['a','b',...,'z']
    if sys.version_info.major == 2:
        init_argumentnames_without_self = inspect.getargspec(func).args[1:]
    else:
        init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:]
    positional_values = args
    keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws)
    attribute_values = positional_values + keyword_values_in_correct_order
    for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values):
        setattr(self,attribute_name,attribute_value)
    # call the original __init__
    func(self, *args, **kws)
class Test():
    @simple_init
    def __init__(self,a,b,c,d=4):
        print(self.a,self.b,self.c,self.d)
#prints 1 3 2 4
t = Test(1,c=2,b=3)
#keeps signature
#prints ['self', 'a', 'b', 'c', 'd']
if sys.version_info.major == 2:
    print(inspect.getargspec(Test.__init__).args)
else:
    print(inspect.signature(Test.__init__))

Antwoord 3, autoriteit 27%

Zoals anderen al hebben gezegd, is de herhaling niet slecht, maar in sommige gevallen kan een benoemde tuple zeer geschikt zijn voor dit soort problemen. Dit vermijdt het gebruik van locals() of kwargs, wat meestal een slecht idee is.

from collections import namedtuple
# declare a new object type with three properties; x y z
# the first arg of namedtuple is a typename
# the second arg is comma-separated or space-separated property names
XYZ = namedtuple("XYZ", "x, y, z")
# create an object of type XYZ. properties are in order
abc = XYZ("one", "two", 3)
print abc.x
print abc.y
print abc.z

Ik heb er een beperkt gebruik van gevonden, maar je kunt een benoemde tuple erven zoals bij elk ander object (voorbeeld vervolg):

class MySuperXYZ(XYZ):
    """ I add a helper function which returns the original properties """
    def properties(self):
        return self.x, self.y, self.z
abc2 = MySuperXYZ(4, "five", "six")
print abc2.x
print abc2.y
print abc2.z
print abc2.properties()

Antwoord 4, autoriteit 27%

Expliciet is beter dan impliciet…
zo zeker, je zou het beknopter kunnen maken:

def __init__(self,a,b,c):
    for k,v in locals().items():
        if k != "self":
             setattr(self,k,v)

De betere vraag is: zou je dat moeten doen?

… dat zei dat als je een tuple met een naam wilt, ik zou aanraden om een ​​tuple met een naam te gebruiken (onthoud dat tuples bepaalde voorwaarden hebben) … misschien wil je een OrderedDict of zelfs gewoon een dict …


Antwoord 5, autoriteit 20%

Om het antwoord van gruszczyuit te breiden, heb ik een patroon gebruikt als:

class X:
    x = None
    y = None
    z = None
    def __init__(self, **kwargs):
        for (k, v) in kwargs.items():
            if hasattr(self, k):
                setattr(self, k, v)
            else:
                raise TypeError('Unknown keyword argument: {:s}'.format(k))

Ik vind deze methode leuk omdat:

  • vermijdt herhaling
  • is bestand tegen typefouten bij het construeren van een object
  • werkt goed met subclassificatie (kan gewoon super().__init(...))
  • maakt documentatie mogelijk van de attributen op klasseniveau (waar ze thuishoren) in plaats van in X.__init__

Vóór Python 3.6 geeft dit geen controle over de volgorde waarin de attributen worden ingesteld, wat een probleem kan zijn als sommige attributen eigenschappen zijn met setters die toegang hebben tot andere attributen.

Het zou waarschijnlijk een beetje verbeterd kunnen worden, maar ik ben de enige gebruiker van mijn eigen code, dus ik maak me geen zorgen over enige vorm van invoersanering. Misschien is een AttributeErrorpassender.


Antwoord 6, autoriteit 9%

Je zou ook kunnen doen:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
    setattr(self, arg, locs[arg])

Natuurlijk moet je de module inspectimporteren.


Antwoord 7, autoriteit 7%

Dit is een oplossing zonder extra invoer.

Helperfunctie

Een kleine hulpfunctie maakt het handiger en herbruikbaar:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    self = local_name_space.pop('self')
    for name, value in local_name_space.items():
        setattr(self, name, value)

Toepassing

Je moet het aanroepen met locals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())

Test

a = A(1, 2, 3)
print(a.__dict__)

Uitvoer:

{'y': 2, 'z': 3, 'x': 1}

Zonder verandering van locals()

Als je locals()niet wilt wijzigen, gebruik dan deze versie:

def auto_init(local_name_space):
    """Set instance attributes from arguments.
    """
    for name, value in local_name_space.items():
        if name != 'self': 
            setattr(local_name_space['self'], name, value)

Antwoord 8, autoriteit 6%

Een interessante bibliotheek die hiermee omgaat (en veel andere standaardteksten vermijdt) is attrs. Uw voorbeeld kan bijvoorbeeld tot dit worden teruggebracht (neem aan dat de klasse MyClassheet):

import attr
@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()

Je hebt niet eens een __init__methode meer nodig, tenzij het ook andere dingen doet. Hier is een mooie introductie door Glyph Lefkowitz.


Antwoord 9, autoriteit 5%

Mijn 0,02$. Het komt heel dicht in de buurt van het antwoord van Joran Beasley, maar eleganter:

def __init__(self, a, b, c, d, e, f):
    vars(self).update((k, v) for k, v in locals().items() if v is not self)

Bovendien kan het antwoord van Mike Müller (de beste naar mijn smaak) worden verminderd met deze techniek:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)

En bel gewoon auto_init(locals())vanuit je __init__


Antwoord 10, autoriteit 4%

Het is een natuurlijke manier om dingen te doen in Python. Probeer niet iets slimmers uit te vinden, het zal leiden tot te slimme code die niemand in je team zal begrijpen. Als je een teamspeler wilt zijn en het dan op deze manier blijft schrijven.


Antwoord 11, autoriteit 4%

Python 3.7 en hoger

In Python 3.7 mag je (ab)gebruik maken van de dataclass-decorator, beschikbaar via de module dataclasses. Uit de documentatie:

Deze module biedt een decorateur en functies voor het automatisch toevoegen van gegenereerde speciale methoden zoals __init__()en __repr__()aan door de gebruiker gedefinieerde klassen. Het werd oorspronkelijk beschreven in PEP 557.

De lidvariabelen die in deze gegenereerde methoden moeten worden gebruikt, worden gedefinieerd met annotaties van het PEP 526-type. Bijvoorbeeld deze code:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0
    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

Zal onder andere een __init__()toevoegen die eruitziet als:

def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0):
      self.name = name
      self.unit_price = unit_price
      self.quantity_on_hand = quantity_on_hand

Houd er rekening mee dat deze methode automatisch aan de klasse wordt toegevoegd: deze wordt niet rechtstreeks gespecificeerd in de bovenstaande InventoryItem-definitie.

Als uw klas groot en complex is, is het mogelijkongepast om een ​​dataclasste gebruiken. Ik schrijf dit op de dag van release van Python 3.7.0, dus gebruikspatronen zijn nog niet goed vastgesteld.

Other episodes