Verwarring in code met overerving, instantiatie, * args, ** kwargs

class Pen(object):
    def __init__(self, mean_radius = None, length = None):
        self.usage = "Is used to apply ink trails to drawing surfaces"
        self.mean_radius = mean_radius
        self.length = length

class FountainPen(Pen):
    def __init__(self, manufacturer = "Waterman", *args, **kwargs):
        Pen.__init__(self, *args, **kwargs)
        self.manufacturer = manufacturer
        self.cartridge_state =  "non-empty"

>>> instance_FP = FountainPen(5, 1)
>>> instance_FP.mean_radius
>>> print instance_FP.mean_radius
1    
>>> print instance_FP.length
None

Wat gebeurt er als het gehele getal 5 wordt doorgegeven als een argument in de instantiatie van FountainPen ? Waarom retourneert instance_FP.mean_radius retourneren 1 en niet 5 ?

3
"Wat gebeurt er ..." Kwam het bij u op om te kijken naar de andere kenmerken van de instantie?
toegevoegd de auteur Karl Knechtel, de bron

4 antwoord

Je moet het zo denken: * args en ** kwargs "eet" alle positionele/trefwoordargumenten achtergelaten door de "reguliere" argumenten die eraan vooraf gaan. Als je ze op het einde plaatst, krijgen ze alleen datgene wat niet in de "reguliere argumenten" past.

Dus, wanneer je FountainPen (5,1) schrijft, gebeurt er dat FountainPen .__ init __ zo wordt genoemd:

  • self is set to the newly created instance;
  • manufacturer gets the first argument, which is 5;
  • *args "eats" all the remaining positional arguments, i.e. just 1; so args is now set to [1];
  • *kwargs would eat any keyword argument left, but there's none, so it becomes {}.

Het is dan duidelijk dat Pen .__ init __ wordt aangeroepen met alleen 1 als argument (naast self ) en length blijft ingesteld op de standaardwaarde ( Geen ).

6
toegevoegd

Een elegantere oplossing zou zijn:

class FountainPen(Pen):
    def __init__(self, *args, **kwargs):
        Pen.__init__(self, *args, **kwargs)
        self.manufacturer = kwargs.get('manufacturer', 'Waterman')
        self.cartridge_state =  "non-empty"

blah = FountainPen(5, 1, manufacturer='Waterman')
2
toegevoegd
@JPvdMerwe Heel goed punt - dacht daar niet aan! Post bewerkt om uw suggestie te weerspiegelen
toegevoegd de auteur Jon Clements, de bron
Dit kan een beetje beter worden opgeknapt als u het gedrag van de standaardwaarde wilt behouden. Gebruik: self.manufacturer = kwargs.get ('fabrikant', 'Waterman')
toegevoegd de auteur JPvdMerwe, de bron

Matteo Italia's mentions what is wrong, basically manufacturer is a positional argument. You can fix it by slightly changing FountainPen's __init__ to the following:

def __init__(self, *args, manufacturer = "Waterman", **kwargs):

Als u dit doet, wordt fabrikant een zoekwoordargument, dus als u dit wilt wijzigen, moet u bellen:

FountainPen(manufacturer="newval")

Opmerking: helaas is deze syntaxis (van alleen zoekwoordargumenten na de parameter * args ) alleen geldig in Python 3. Jon Clements heeft een oplossing die dit probleem voor Python 2 oplost.

2
toegevoegd

In uw code is 5 toegewezen aan het argument fabrikant en 1 gaat naar * args .

Bij het doorgeven van argumenten zonder sleutels aan een functie, moet u eerst de expliciete opgeven. De rest wordt doorgegeven aan * args. Om dit te voorkomen, geeft u trefwoorden op wanneer u uw constructeur belt:

>>> instance_FP = FountainPen(mean_radius=5, length=1)
0
toegevoegd
@JonClements Ik heb het opgelost.
toegevoegd de auteur Karl Knechtel, de bron
Typ op lengte
toegevoegd de auteur Jon Clements, de bron