Licence CC BY-NC-ND, Thierry Parmentelat & Arnaud Legout

from IPython.display import HTML
HTML(filename="_static/style.html")

fonctions lambda#

lambda est une expression, pas une instruction
qui permet de créer un objet fonction anonyme et à la volée

# un objet fonction 
# qui n'a pas de nom
lambda x: x**2
<function __main__.<lambda>(x)>
# mais que je peux appeler
(lambda x: x**2)(10)
100
# comme si j'avait fait
def anonymous(x):
    return x**2

anonymous(10)
100

lambda : une expression#

  • elle peut donc apparaître
    là où une fonction classique ne peut pas

  • typiquement à l’intérieur d’une expression

  • par contre pas trop adapté pour du code compliqué

    • doit pouvoir être écrit sous forme d’une seule expression

lambda vs def#

  • les deux formes suivantes sont donc équivalentes

def foo(x):
    return x*x
foo
<function __main__.foo(x)>
foo = lambda x: x*x
foo
<function __main__.<lambda>(x)>

limitation de lambda#

  • le corps d’une fonction lambda
    doit tenir sur une seule expression

    • uniquement applicable à des fonctions simples

    • pas de déclaration de variable locale

    • pas d’impact sur la portée des variables

  • forme générale

lambda arg1, arg2,  argN : expression using args
f = lambda x: x+1
f(1)
2
(lambda x: x+1)(12)
13

exemples d’utilisation#

# pour appeler un objet fonction
# c'est la syntaxe habituelle
def call(func, a, b):
    return(func(a, b))

# j'appelle l'addition sur ces deux nombres
call(lambda a, b: a + b, 3, 5)
8
# pareil avec la multiplication
call(lambda a, b: a * b, 3, 5)
15

application au tri#

Rappel :

  • sort() est une méthode qui trie les listes en place

  • sorted() est une fonction built-in qui trie n’importe quel itérable et retourne un nouvel itérateur

  • argument key:
    une fonction pour spécifier le critère de tri
    typiquement une fonction lambda

  • argument reverse:
    booléen qui définit l’ordre de tri
    par défaut reverse=False: tri ascendant

sample = "This is a test string from Andrew".split()
sample
['This', 'is', 'a', 'test', 'string', 'from', 'Andrew']
# une copie triée de sample
sorted(sample)
['Andrew', 'This', 'a', 'from', 'is', 'string', 'test']
# avec un autre critère
sorted(sample, key=str.lower)
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']
# pareil que
sorted(sample, key=lambda s: str.lower(s))
['a', 'Andrew', 'from', 'is', 'string', 'test', 'This']

ou encore avec une autree donnée

student_marks = [('marc', 12), ('eric', 15), ('jean', 12), ('gabriel', 18)]

# on n'indique pas comment trier : c'est l'ordre "naturel" des tuples
sorted(student_marks)
[('eric', 15), ('gabriel', 18), ('jean', 12), ('marc', 12)]
# pour trier sur la note cette fois
sorted(student_marks, key=lambda student_tuple: student_tuple[1])
[('marc', 12), ('jean', 12), ('eric', 15), ('gabriel', 18)]
# remarque:
# des utilitaires sont disponibles aussi
# dans le module standard `operator` 
import operator
sorted(student_marks, key=operator.itemgetter(1))
[('marc', 12), ('jean', 12), ('eric', 15), ('gabriel', 18)]

pour en savoir plus

pour aller plus loin sur le tri, voir https://docs.python.org/3/howto/sorting.html

reverse() et reversed()#

  • un schéma analogue existe
    pour renverser / retourner une liste

  • list.reverse() comme méthode
    sur les listes qui retourne en place

  • reversed() comme fonction builtin qui renvoie
    un itérateur pour parcourir à l’envers

source = [1, 10, 100, 1000]
source.reverse()
source
[1000, 100, 10, 1]
reversed(source)
<list_reverseiterator at 0x7f220ae31540>
list(reversed(source))
[1, 10, 100, 1000]

map() et filter()#

rappel : appliquent une fonction à un itérable

  • map(func, iter)

    • applique la fonction func à chaque élément de iter

    • retourne (un itérateur sur) les valeurs retournées par func

  • filter(func, iter)

    • similaire à map, mais retourne seulement les valeurs
      qui sont vraies (techniquement, telles que bool(val) == True)

L = [1, 2, 3, 4]
m = map(lambda x: x**2, L)
# le résultat est un itérateur
m
<map at 0x7f220ae314b0>
# pour voir le résultat la liste on
# peut par exemple transformer
# explicitement en list()
list(m)              
[1, 4, 9, 16]
# si on essaie une deuxième fois
# il ne se passe plus rien car on a déjà itéré sur m
list(m)          
[]
m = map(lambda x: x**2, L) 
for i in m: 
    print(i, end=' ')
1 4 9 16 
source = range(1, 5)
f = filter(lambda x: x%2 == 0, 
           map(lambda x:x**2, source))

# f est bien un itérateur
f is iter(f)   
True
list(f)
[4, 16]

cette forme est toutefois passée de mode au profit des expressions génératrices

# ceci est vraiment équivalent 
g = (x**2 for x in source
     if x%2 == 0)

g is iter(g)
True
list(g)
[4, 16]

introspection (avancé)#

  • en Python tout est un objet
    on peut accéder à tous les attributs d’un objet

  • en particulier donc pour les objets de type fonction

  • modifier directement les attributs n’est pas recommandé,
    sauf si on comprend vraiment ce que l’on fait

  • par contre c’est intéressant en lecture
    pour comprendre les détails d’implémentation

lecture d’attributs#

 def f(name="jean"):
    """le docstring"""
    pass
f.__name__
'f'
f.__doc__
'le docstring'
f.__code__
<code object f at 0x7f22200f2c10, file "/tmp/ipykernel_1061/3808594940.py", line 1>
f.__module__
'__main__'
f.__defaults__
('jean',)

aller plus loin#