En lisant l' introduction pratique de Mary Rose Cook à la programmation fonctionnelle , elle donne comme exemple un anti-modèle
def format_bands(bands):
for band in bands:
band['country'] = 'Canada'
band['name'] = band['name'].replace('.', '')
band['name'] = band['name'].title()
depuis
- la fonction fait plus d'une chose
- le nom n'est pas descriptif
- il a des effets secondaires
Comme solution proposée, elle suggère de canaliser les fonctions anonymes
pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])
Cependant, cela me semble avoir l'inconvénient d'être encore moins testable; au moins format_bands pourrait avoir un test unitaire pour vérifier s'il fait ce qu'il est censé faire, mais comment tester le pipeline? Ou est-ce l'idée que les fonctions anonymes sont si explicites qu'elles n'ont pas besoin d'être testées?
Mon application réelle pour cela est d'essayer de rendre mon pandas
code plus fonctionnel. Je vais souvent avoir une sorte de pipeline à l'intérieur d'une fonction "munging" "
def munge_data(df)
df['name'] = df['name'].str.lower()
df = df.drop_duplicates()
return df
Ou réécriture dans le style pipeline:
def munge_data(df)
munged = (df.assign(lambda x: x['name'].str.lower()
.drop_duplicates())
return munged
Des suggestions de bonnes pratiques dans ce genre de situation?
la source
Réponses:
Je pense que vous avez probablement manqué la partie la plus importante de l'exemple corrigé du livre. Le changement le plus fondamental du code passe de la méthode fonctionnant sur toutes les valeurs d'une liste à fonctionnant sur un élément.
Il existe déjà des fonctions comme
iter
(dans ce cas nommépipeline_foreach
) qui effectuent une opération donnée sur tous les éléments d'une liste. Il n'était pas nécessaire de dupliquer cela avec unefor
boucle. L'utilisation d'une opération de liste bien connue rend également votre intention claire. Avecmap
vous transformez les valeurs. Aveciter
vous effectuez un effet secondaire avec chaque élément. Avec lafor
boucle, vous êtes ... eh bien, vous ne savez pas vraiment jusqu'à ce que vous regardiez à travers.L'exemple de code corrigé n'est toujours pas très fonctionnel, car il (pour autant que je sache) mute les valeurs de la liste sans les renvoyer, ce qui empêche la composition de la tuyauterie ou des fonctions. La méthode fonctionnellement préférée
map
créerait une nouvelle liste de bandes avec les mises à jourcountry
etname
. Ensuite, vous pouvez diriger cette sortie vers la fonction suivante ou composermap
avec une autre fonction qui a pris une liste de bandes. Aveciter
, c'est comme une impasse de pipelining.Je pense que le code du résultat final a de petites fonctions qui sont trop triviales pour déranger les tests ici. Après tout, vous ne devriez pas avoir besoin d'écrire des tests unitaires contre
replace
outitle
. Maintenant, vous voulez peut-être les composer ensemble dans votre propre test de fonction et d'unité que la combinaison souhaitée est obtenue sur un seul élément. Moi-même, je serais probablement passéformat_bands
auformat_band
singulier, abandonné la boucle for et appelépipeline_each(bands, format_band)
. Ensuite, vous pouvez tester format_band pour vous assurer que vous n'avez pas oublié quelque chose.Quoi qu'il en soit, passez à votre code. Votre deuxième exemple de code semble plus pipeline-y. Mais cela seul ne fournit pas les avantages d'une programmation fonctionnelle. En pratique, la programmation fonctionnelle signifie assurer la compatibilité des fonctions avec d'autres fonctions en définissant leur compatibilité uniquement en termes d'entrées et de sorties. S'il y a des effets secondaires cachés à l'intérieur de la fonction, alors malgré son entrée / sortie alignée avec une autre fonction, vous ne pouvez pas savoir s'ils sont compatibles jusqu'à l'exécution. Si toutefois, deux fonctions sont sans effet secondaire et correspondent à la sortie-à-l'entrée, vous pouvez les canaliser ou les composer avec peu de soucis de résultats inattendus.
la source