kwargs als argument

kwargs: use or misuse?

Elke python-programmeur kent het kwargs concept, waarmee een variabel aantal named- (of ‘keyword’) argumenten ontvangen kan worden in een functie. Soms wordt dit concept echter onhandig gebruikt.

In een ontvangende functie gebruikt men vaak de parameter-naam kwargs (dus als: ‘formal parameter’); al is deze naam niet verplicht, het gaat om de ‘**’-syntax. Soms wordt die naam ook gebruikt als argument; dus bij het aanroepen van die functie. Dat is natuurlijk niet nodig; het gaat juist om alle andere (named) argumenten; die verzameld worden in kwargs

Tip

Wat is ook al weer een parameter en wat een argument?

Taal-specialisten maken een verschil tussen parameters en argumenten, veel programmeurs in de praktijk. Dat kan verwarend zijn. Immers: kwargs is een afkorting van keyword-arguments. En een veel gebruike naam voor de parameter om die variable-aantal named-argumenten te “verzamelen”.

See also

De Python-FAQ voor het verschil tussen parameters en argumenten.

Natuurlijk is het soms handig meerdere ”waarden” in één data-object te verzamelen voordat die als argument doorgegeven wordt. Zeker in web-frameworks (zoals Flask en Django) gebeurd dat vaak. Een typisch functie berekend dan allerlei gegevens, die daarna aan een template-engines (zie oa. Jinja2) doorgegeven worden. Omdat elke template anders is, zijn de parameters (zowel aantal, naam als structuur) niet vooraf bekend. Toch is het niet nodig om het kwargs-concept in dit soort gevallen te gebruiken! Met een gewone dict kan het ook.

Toch is het kwargs-concept vaak te prefereren; het geeft de gebruikers vrijheid. De aanroepende functie kan een dict gebruiken, om die waarden te verzamelen. Of named-parameters gebruiken; of een mix…

Die vrijheid wordt soms misbruikt, of verkeerd begrepen. Dan start elke functie bijvoorbeeld met het declaren van die kwargs variabele, als een lege dict. Daarna die gevuld, soms al op de volgende regels, en met constanten of eenvoudige expressies. Een voorbeeld:

def demo_waarom():
    """Waarom hier `kwargs` gebruiken?"""
    kwargs = {}
    kwargs['start'] = datetime.datetime.now()
    kwargs['end']   = kwargs['start'] + datetime.timedelta(days=7)
    kwargs['DEMO']  = "Kan beter"
    ...
    return render_template('aWeek.html', **kwargs)

Waarom? En hoe beter?

Mijn vraag bij bovenstaande code is vaak waarom? Waarom heet die variabele kwargs? Maar ook: waarom deze constructie. Immers de code kan eenvoudig korter en leesbaarder gemaakt worden. Soms kan de helft gewoon weg!

Als zal dat niet altijd kunnen, daarom laat ik hieronder in een paar stappen zien hoe het beter kan.

Andere naam

De eerste stap is een de naam kwargs vervangen door iets beters. Een beschrijvende naam, die aangeeft om welke waarden het gaat is vaak te prefereren. Al is dat soms lastig, zeker in een demo of een korte functie. In dat laatste geval kies ik zelf vaak voor d als afkorting van dict. In dit geval lijkt het om week-info te gaan; en gebruik in de naam ‘week’.

def demo_andere_naam():
    """Bijna elke variabele-naam is beter dan `kwargs`"""
    week = {}
    week['start'] = datetime.datetime.now()
    week['end']   = week['start'] + datetime.timedelta(days=7)
    week['DEMO']  = "Al iets beter"
    ...
    return render_template('aWeek.html', **week)

Vul de dict direct

In bovenstaande code zijn 4 regels nodig om een dictionary te vullen, waarbij de naam van de dict telkens herhaald wordt. Dat lastig te onderhouden en niet nodig. De meeste waarde kunnen immers direct in die dictionary gezet worden.

def demo_vul_direct():
    """Een lege dict is niet nodig; waarom niet direct de waarden invullen?"""
    now = datetime.datetime.now()
    week = {'start': now,
            'end'  :  now + datetime.timedelta(days=7),
            'DEMO' : "Beter te onderhouden"}
    ...
    return render_template('aWeek.html', **week)

Zonder kwargs!

In een paar stapje is de code korter, leesbaarder en onderhoud-vriendelijk(er) geworden. Maar het kwargs-concept wordt nog steeds (mis)bruikt. Dat is echter niet nodig. Het enige dat nodig is, zijn de start en end datums van de week en een DEMO string. Door die als named parameter door te geven wordt de code nog korter en leesbaarder. En is de kwargs (cq week) variabele niet nodig.

def demo_zonder_kwargs():
    """De kwargs/week dict is helemaal niet nodig!"""
    now = datetime.datetime.now()
    return render_template('aWeek.html', start=now, end=now+datetime.timedelta(days=7), DEMO="kwargs is niet nodig")

Niet alleen is deze code meer dan 60% korter, ook is het veel duidelijker welke waarden doorgegeven worden. Het is heel duidelijk geworden dat de parameters start, end en DEMO gebruikt worden om die html-pagina te maken.

Conclusie

We hebben gezien dat het kwargs-concept heel handig is – veel frameworks gebruiken het dan ook en gebruiken kwargs als formale parameter naam. We hebben ook gezien dat die naam als variabele-naam niet heel veel duidelijkheid geeft. Bijna altijd is er een betere naam te bedenken.

Belangrijker is hoe we zo’n functie aanroepen en –waar nodig– de dict vullen. Constante waarden, of waarden die met een eenvoudig expressies te bereken zijn, kunnen met direct gebruikt worden. Ofwel als named-parameter, of (‘literal’) bij het maken van de dict. Soms is meer code nodig, vaak is een gewone lokale variabele dan het handigste. Die als named parameter doorgegeven kan worden.

Door een combinatie van deze standaad denkstappen en een streven om de code leesbaar te maken wordt de code vaak veel onderhoudbaarder en meestal korter.

Comments

comments powered by Disqus