
En Python, vous pouvez spécifier une fonction ou un callable pour le paramètre key dans les fonctions intégrées triées(), max(), min(), etc.
Cet article décrit le contenu suivant.
- Spécifiez une fonction intégrée pour le paramètre clé
- Spécifiez une expression lambda ou votre propre fonction pour le paramètre clé
- Spécifiez operator.itemgetter() pour le paramètre clé
- Spécifiez operator.attrgetter() pour le paramètre clé
- Spécifiez operator.methodcaller() pour le paramètre clé
- Comparaison de vitesse entre l’expression lambda et operator.itemgetter().
Voir également l’article suivant pour des exemples d’utilisation du paramètre clé.
Spécifiez une fonction intégrée pour le paramètre clé
Un exemple simple d’utilisation du paramètre clé consiste à spécifier une fonction intégrée.
Par défaut, dans sorted(), les éléments de la liste sont comparés et triés tels quels.
l = [1, -3, 2]
print(sorted(l))
# [-3, 1, 2]
Si abs() qui renvoie une valeur absolue est spécifié pour le paramètre clé, les éléments sont triés par la valeur absolue de chaque élément.
Notez que les parenthèses () sont inutiles lorsqu’une fonction ou un autre appelable est spécifié comme argument.
print(sorted(l, key=abs))
# [1, 2, -3]
La fonction spécifiée dans key n’est utilisée que dans la comparaison et les éléments du résultat restent les mêmes. Si vous souhaitez appliquer une fonction à un élément et le convertir, utilisez les compréhensions de liste.
l_abs = [abs(i) for i in l]
print(l_abs)
# [1, 3, 2]
print(sorted(l_abs))
# [1, 2, 3]
Il en va de même pour la méthode sort() des listes.
l.sort(key=abs)
print(l)
# [1, 2, -3]
Vous pouvez également spécifier le paramètre clé dans max() et min().
l = [1, -3, 2]
print(max(l))
# 2
print(max(l, key=abs))
# -3
print(min(l))
# -3
print(min(l, key=abs))
# 1
Notez que key est un paramètre de mot-clé uniquement, il doit donc toujours être spécifié comme key=xxx.
Les exemples suivants utilisent sorted(), mais l’utilisation du paramètre key est la même pour sort(), max(), min(), etc.
Un autre exemple est le cas d’une liste de chaînes. Par défaut, la liste est triée par ordre alphabétique, mais elle peut être triée par ordre de nombre de caractères en spécifiant len() comme paramètre clé.
l_str = ['bbb', 'c', 'aa']
print(sorted(l_str))
# ['aa', 'bbb', 'c']
print(sorted(l_str, key=len))
# ['c', 'aa', 'bbb']
Spécifiez une expression lambda ou votre propre fonction pour le paramètre clé
Vous pouvez spécifier non seulement des fonctions intégrées, mais également des expressions lambda ou vos propres fonctions définies avec def pour le paramètre clé.
Utilisez une liste à deux dimensions (liste de listes) comme exemple.
Lors de la comparaison de listes, le premier élément est comparé en premier.
l_2d = [[2, 10], [1, -30], [-3, 20]]
print(sorted(l_2d))
# [[-3, 20], [1, -30], [2, 10]]
En spécifiant max() pour l’argument clé, les listes sont triées en fonction de leurs valeurs maximales.
print(sorted(l_2d, key=max))
# [[1, -30], [2, 10], [-3, 20]]
Si vous souhaitez trier en fonction de la valeur absolue maximale de chaque liste, utilisez une expression lambda.
print(sorted(l_2d, key=lambda x: max([abs(i) for i in x])))
# [[2, 10], [-3, 20], [1, -30]]
Notez que vous n’avez pas à vous inquiéter si le nombre d’éléments dans la liste est petit, mais vous pourrez peut-être réduire l’utilisation de la mémoire en utilisant une expression de générateur pour max().
print(sorted(l_2d, key=lambda x: max(abs(i) for i in x)))
# [[2, 10], [-3, 20], [1, -30]]
Vous pouvez définir une fonction avec def au lieu d’une expression lambda et la spécifier pour key.
def max_abs(x):
return max(abs(i) for i in x)
print(sorted(l_2d, key=max_abs))
# [[2, 10], [-3, 20], [1, -30]]
Spécifiez operator.itemgetter() pour le paramètre clé
itemgetter() dans l’opérateur de bibliothèque standard renvoie un objet appelable qui récupère un élément de liste ou une valeur de dictionnaire.
Vous triez une liste bidimensionnelle en fonction de la valeur de n’importe quelle position (index) avec operator.itemgetter().
import operator
l_2d = [[2, 10], [1, -30], [-3, 20]]
print(sorted(l_2d, key=operator.itemgetter(1)))
# [[1, -30], [2, 10], [-3, 20]]
Puisque operator.itemgetter(xxx) renvoie un callable, spécifiez-le comme key=operator.itemgetter(xxx).
f = operator.itemgetter(1)
print(f([2, 10]))
# 10
print(operator.itemgetter(1)([2, 10]))
# 10
Vous pouvez faire la même chose avec une expression lambda.
print(sorted(l_2d, key=lambda x: x[1]))
# [[1, -30], [2, 10], [-3, 20]]
operator.itemgetter() est plus rapide que l’expression lambda.
Le résultat d’une simple comparaison de la vitesse de traitement entre operator.itemgetter() et operator.itemgetter() est décrit à la fin.
operator.itemgetter() peut également être utilisé pour le dictionnaire dict.
Une liste de dictionnaires avec une clé commune est utilisée comme exemple. Les dictionnaires ne peuvent pas être comparés les uns aux autres, donc une erreur est levée par défaut, mais operator.itemgetter() peut être utilisé pour trier la liste en fonction de la valeur de la clé donnée.
l_dict = [{'k1': 2, 'k2': 10}, {'k1': 1}, {'k1': 3}]
# print(sorted(l_dict))
# TypeError: '<' not supported between instances of 'dict' and 'dict'
print(sorted(l_dict, key=operator.itemgetter('k1')))
# [{'k1': 1}, {'k1': 2, 'k2': 10}, {'k1': 3}]
Notez qu’une erreur est générée si un dictionnaire sans la clé spécifiée est inclus.
# print(sorted(l_dict, key=operator.itemgetter('k2')))
# KeyError: 'k2'
Vous pouvez faire la même chose avec une expression lambda.
print(sorted(l_dict, key=lambda x: x['k1']))
# [{'k1': 1}, {'k1': 2, 'k2': 10}, {'k1': 3}]
Si le dictionnaire n’a pas la clé spécifiée, vous pouvez la remplacer par n’importe quelle valeur avec la méthode get(). Voir l’article suivant.
Si plusieurs arguments sont spécifiés à operator.itemgetter(), un tuple contenant le résultat de chacun est renvoyé.
l_dict = [{'k1': 2, 'k2': 'ccc'}, {'k1': 1, 'k2': 'ccc'}, {'k1': 2, 'k2': 'aaa'}]
print(operator.itemgetter('k1', 'k2')(l_dict[0]))
# (2, 'ccc')
Les tuples sont également comparés dans l’ordre à partir du premier élément comme les listes.
print(sorted(l_dict, key=operator.itemgetter('k1')))
# [{'k1': 1, 'k2': 'ccc'}, {'k1': 2, 'k2': 'ccc'}, {'k1': 2, 'k2': 'aaa'}]
print(sorted(l_dict, key=operator.itemgetter('k1', 'k2')))
# [{'k1': 1, 'k2': 'ccc'}, {'k1': 2, 'k2': 'aaa'}, {'k1': 2, 'k2': 'ccc'}]
print(sorted(l_dict, key=operator.itemgetter('k2', 'k1')))
# [{'k1': 2, 'k2': 'aaa'}, {'k1': 1, 'k2': 'ccc'}, {'k1': 2, 'k2': 'ccc'}]
Vous pouvez également faire la même chose avec une expression lambda.
print(sorted(l_dict, key=lambda x: (x['k1'], x['k2'])))
# [{'k1': 1, 'k2': 'ccc'}, {'k1': 2, 'k2': 'aaa'}, {'k1': 2, 'k2': 'ccc'}]
Spécifiez operator.attrgetter() pour le paramètre clé
operator.attrgetter() renvoie un objet appelable qui récupère un attribut.
Utilisez une liste d’objets datetime.date comme exemple. Vous pouvez obtenir le jour, le mois et l’année avec les attributs jour, mois et année de datetime.date.
import datetime
l_dt = [datetime.date(2003, 2, 10), datetime.date(2001, 3, 20), datetime.date(2002, 1, 30)]
print(l_dt[0])
# 2003-02-10
print(l_dt[0].day)
# 10
f = operator.attrgetter('day')
print(f(l_dt[0]))
# 10
Par défaut, ils sont triés par date, mais vous pouvez trier par n’importe quel attribut avec operator.attrgetter().
print(sorted(l_dt))
# [datetime.date(2001, 3, 20), datetime.date(2002, 1, 30), datetime.date(2003, 2, 10)]
print(sorted(l_dt, key=operator.attrgetter('day')))
# [datetime.date(2003, 2, 10), datetime.date(2001, 3, 20), datetime.date(2002, 1, 30)]
Bien que operator.attrgetter() soit plus rapide, cela peut également être fait avec une expression lambda.
print(sorted(l_dt, key=lambda x: x.day))
# [datetime.date(2003, 2, 10), datetime.date(2001, 3, 20), datetime.date(2002, 1, 30)]
Spécifiez operator.methodcaller() pour le paramètre clé
operator.methodcaller() renvoie un objet appelable qui appelle une méthode.
Utilisez la méthode find(), qui renvoie la position d’une chaîne donnée, par exemple.
l_str = ['0_xxxxA', '1_Axxxx', '2_xxAxx']
print(l_str[0])
# 0_xxxxA
print(l_str[0].find('A'))
# 6
f = operator.methodcaller('find', 'A')
print(f(l_str[0]))
# 6
Par défaut, il est trié par ordre alphabétique, mais vous pouvez trier en fonction des résultats de n’importe quelle méthode avec operator.methodcaller().
print(sorted(l_str))
# ['0_xxxxA', '1_Axxxx', '2_xxAxx']
print(sorted(l_str, key=operator.methodcaller('find', 'A')))
# ['1_Axxxx', '2_xxAxx', '0_xxxxA']
Bien que operator.attrgetter() soit plus rapide, cela peut également être fait avec une expression lambda.
print(sorted(l_str, key=lambda x: x.find('A')))
# ['1_Axxxx', '2_xxAxx', '0_xxxxA']
Comparaison de vitesse entre l’expression lambda et operator.itemgetter().
Cette section montre les résultats d’une simple comparaison de vitesse entre les expressions lambda et operator.itemgetter().
Utilisez une liste de dictionnaires avec une clé commune (10000 éléments) comme exemple.
import operator
l = [{'k1': i} for i in range(10000)]
print(len(l))
# 10000
print(l[:5])
# [{'k1': 0}, {'k1': 1}, {'k1': 2}, {'k1': 3}, {'k1': 4}]
print(l[-5:])
# [{'k1': 9995}, {'k1': 9996}, {'k1': 9997}, {'k1': 9998}, {'k1': 9999}]
Notez que le code ci-dessous utilise la commande magique Jupyter Notebook %%timeit et ne fonctionne pas lorsqu’il est exécuté en tant que script Python.
%%timeit
sorted(l, key=lambda x: x['k1'])
# 1.09 ms ± 35 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
sorted(l, key=operator.itemgetter('k1'))
# 716 µs ± 28.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
sorted(l, key=lambda x: x['k1'], reverse=True)
# 1.11 ms ± 41.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
sorted(l, key=operator.itemgetter('k1'), reverse=True)
# 768 µs ± 58.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
max(l, key=lambda x: x['k1'])
# 1.33 ms ± 130 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
max(l, key=operator.itemgetter('k1'))
# 813 µs ± 54.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
min(l, key=lambda x: x['k1'])
# 1.27 ms ± 69.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%%timeit
min(l, key=operator.itemgetter('k1'))
# 824 µs ± 83.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
operator.itemgetter() est plus rapide que les expressions lambda pour toutes les fonctions triées(), max() et min().
Bien entendu, les résultats peuvent varier en fonction de l’environnement et des conditions (nombre d’éléments, etc.).
