Sam & Max » fonction http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Introduction au currying 3 http://sametmax.com/introduction-au-currying/ http://sametmax.com/introduction-au-currying/#comments Fri, 12 Dec 2014 19:37:00 +0000 http://sametmax.com/?p=12693 Le currying (ou Curryfication pour les frencofans) est le nom donné à une technique de programmation qui consiste à créer une fonction à partir d’une autre fonction et d’une liste partielle de paramètres destinés à celle-ci. On retrouve massivement cette technique en programmation fonctionnelle puisqu’elle permet de créer une fonction pure à partir d’une autre fonction pure. C’est une forme de réutilisabilité de code.

La forme la plus simple de currying est de réécrire une fonction appelant l’autre. Par exemple, soit une fonction pour multiplier tous les éléments d’un itérable :

def multiply(iterable, number):
    """ Multiplie tous les éléments d'un itérable par un nombre.
 
        Exemple :
 
            >>> list(multiply([1, 2, 3], 2))
            [2, 4, 6]
    """
    return (x * number for x in iterable)

On peut ensuite créer une fonction qui multipliera par 2 tous les éléments d’un itérable :

def doubled(iterable):
    """ Multiplie tous les éléments d'un itérable par un 2.
 
        Exemple :
 
            >>> list(doubled([1, 2, 3]))
            [2, 4, 6]
    """
    return multiply(iterable, 2)

C’est une forme de currying. On créé une fonction qui fait ce que fait une autre fonction, mais avec des arguments par défaut.

Python possède une fonction pour faire ça automatiquement avec n’importe quelle fonction :

>>> from functools import partial 
>>> tripled = partial(multiply, number=3) # on curryfie ici
>>> list(tripled([1, 2, 3])) # nouvelle fonction avec un seul argument
[3, 6, 9]

Cela marche car, je vous le rappelle, les fonctions sont des objets en Python. On peut mettre une fonction (je ne parle pas de son résultat) dans une variable, passer une fonction en paramètre ou retourner une fonction dans une autre fonction. Les fonctions sont manipulables.

Il n’est pas rare d’utiliser les fonctions anonymes comme outils curryfication. En Python, on ferait ça avec une lambda :

>>> tripled = lambda x: multiple(x, 3) 
>>> list(tripled([1, 2, 3]))
[3, 6, 9]

Certains outils, comme Ramda en Javascript, vont plus loin, et exposent des fonctions qui se curryfient automatiquement.

Pour ce faire, il faut inverser l’ordre qu’on mettrait intuitivement aux arguments dans la déclaration d’une fonction :

# au lieu de multiply(iterable, number), on a :
def multiply(number, iterable=None):
    # Si on a pas d'itérable passé, on curryfie
    if iterable is None:
        return partial(multiply, number=number)
    return (x * number for x in iterable)

Ainsi :

>>> list(multiply(2, [1, 2, 3])) # pas de currying
[2, 4, 6]
>>> quintuple = multiply(5) # currying automatique
>>> list(quintuple([1, 2, 3]))
[5, 10, 15]

L’intérêt de ce style, c’est qu’on peut composer des traitements à partir de plusieurs sous traitements, presque déclarativement :

def remove(filter, iterable=None):
    """ Retire tous les éléments d'un itérable correspondant au filtre.
 
        Exemple :
 
            >>> list(remove(lambda x: x >= 4, [1, 2, 3, 4, 5]))
            [1, 2, 3]
    """
    if iterable is None:
        return partial(remove, filter)
 
    return (x for x in iterable if not filter(x))
 
>>> smalls = remove(lambda x: x >= 4)
>>> list(smalls(tripled([0, 1, 2, 3, 4]))) # le traitement est auto descriptif
[0, 3]

Néanmoins, il faut savoir que ce style n’est pas pythonique. En effet, en Python on préférera généralement utiliser des suites suite de générateurs. Soit par intention, soit via yield.

Notre exemple serait alors :

>>> tripled = (x * 3 for x in [0, 1, 2, 3, 4])
>>> smalls = (x for x in tripled if x <= 4)
>>> list(smalls)
[0, 3]

De plus, cette technique suppose qu’on ne profitera pas de certaines fonctionnalités, comme les paramètres par défaut des fonctions Python.

C’est toutefois une bonne chose à connaître. C’est occasionnellement utile en Python et peut produire des solutions très élégantes. C’est également une bonne chose à comprendre pour aborder d’autres langages plus fonctionnels qui les utilisent bien plus comme le Javascript, le Lisp, ou carrément le Haskell.

]]>
http://sametmax.com/introduction-au-currying/feed/ 3
Mettez des fonctions dans vos structures de données 9 http://sametmax.com/mettez-des-fonctions-dans-vos-structures-de-donnees/ http://sametmax.com/mettez-des-fonctions-dans-vos-structures-de-donnees/#comments Sat, 15 Feb 2014 09:50:04 +0000 http://sametmax.com/?p=9154 En Python les fonctions sont des objets de premier ordre. On peut les manipuler, copier les références, les supprimer…

Exemple :

# On definit une fonction.
# Jusqu'ici tout va bien...
def pizza():
    return "miam"
 
 
print(pizza())
## miam
 
# On peut assigner la référence de
# la fonction à une variable.
delice = pizza
 
# Et utiliser la fonction depuis la variable
print(delice())
## miam
 
# On peut même supprimer la variable
# originale.
del pizza
print(delice())
## miam
 
pizza()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-dc0f1f08233f> in <module>()
----> 1 pizza()
 
NameError: name 'pizza' is not defined

La fonction est un objet ordinaire qui peut donc se mettre dans une variable, mais aussi se passer en paramètre, se retourner comme valeur, etc. J’ai déjà expliqué ça dans le tuto sur les décorateurs plus en détail, mais un rappel ne fait pas de mal.

Cela veut dire que l’on peut mettre la référence d’une fonction dans une structure de données.

Par exemple dans une liste :

def pizza():
    return "miam"
 
def epinard():
    return "miam aussi"
 
def arepas():
    return "decidement tout est miam"
 
# On met les fonctions dans la liste :
liste_de_fonctions = [pizza, epinard, arepas]
 
# Et du coup on peut boucler sur les fonctions
for func in liste_de_fonctions:
    print(func())
 
## miam
## miam aussi
## decidement tout est miam
 
# Le monde des listes s'ouvre à vous
 
print(liste_de_fonctions[-1]())
## decidement tout est miam
 
f1, f2 = liste_de_fonctions[:2]
print(f2())
## miam aussi

Mais aussi dans un dictionnaire :

dico_de_fonction = {
    "choix1": pizza,
    "choix2": epinard,
    "choix3": arepas
}
 
print(dico_de_fonction["choix2"]())
## miam aussi
 
for key, func in dico_de_fonction.items():
    print("%s: %s" % (key, func()))
 
## choix1: miam
## choix3: decidement tout est miam
## choix2: miam aussi

En fait, ça marche avec tout (les fonctions sont hashables) : les sets, les tuples, les deques

On peut même faire un générateur qui yield des fonctions :

import random
 
def fonctions_aleatoires(nombre, fonctions=liste_de_fonctions):
    for x in range(nombre):
        yield random.choice(liste_de_fonctions)
 
for func in fonctions_aleatoires(5):
    print(func())
 
## miam aussi
## miam
## decidement tout est miam
## miam
## miam aussi

Python n’a certes pas les capacités de Lisp ou même Javascript pour faire de la programmation fonctionnelle, mais les fonctions restent des objets très puissants dans ce langage.

D’ailleurs, cet article est valable aussi pour les classes et les modules :)

]]>
http://sametmax.com/mettez-des-fonctions-dans-vos-structures-de-donnees/feed/ 9
Le module operator en Python 9 http://sametmax.com/le-module-operator-en-python/ http://sametmax.com/le-module-operator-en-python/#comments Sat, 12 Jan 2013 10:17:33 +0000 http://sametmax.com/?p=4102 operator contient des fonctions qui font ce que font les opérateurs. ]]> Le module operator contient des fonctions qui font ce que font les opérateurs. Exemple :

>>> from operator import add, gt
>>> add(1, 1) # fait en fonction ce que ferait 1 + 1
>>> gt(1, 2) # fait en fonction ce que ferait 1 > 2

On a donc dans le module operator de quoi remplacer les opérateurs +, <, not, etc. par des fonctions.

Les opérateurs or et and n'ont pas de fonctions équivalentes car ils short circuitent, ce que ne peut pas faire une fonction. operator.and_ et operator.or_ sont des fonctions pour & et |, par pour or et and.

Il y a des opérateurs qu'on oublie souvent, comme l'opérateur []. Le module operator contient des fonctions pour lui également.

>>> from operator import getitem, getslice
>>> lst = [1, 2, 3]
>>> lst[0]
1
>>> getitem(lst, 0)
1
>>> lst[:2]
[1, 2]
>>> getslice(lst, 0, 2)
[1, 2]

On oublie parfois l'opérateur ".". Il y a une fonction builtin pour ça:

>>> class Classe(object):
...     def __init__(self, type):
...         self.type = type
...         
>>> c = Classe('cm2')
>>> c.type
'cm2'
>>> getattr(c, 'type')
'cm2'

Mais le plus étonnant dans le module operator, c'est qu'il a des fonctions qui retournent... des fonctions.

>>> from operator import itemgetter
>>> getfirst = itemgetter(0)
>>> getfirst([1, 2, 3])
1
>>> getfirst('abc')
'a'
>>> getsecond = itemgetter(1)
>>> getsecond([1, 2, 3])
2
>>> getsecond("abc")
'b'
>>> getlast = itemgetter(-1)
>>> getlast(range(100))
99

Ici on créer des fonctions qui ont pour seul triste but dans la vie de retourner l'élément à l'index donné. getfirst retournera l'élément 0. getsecond retournera l'élément à l'index 1. getlast retournera l'élément à l'index -1. itemgetter() est un créateur de fonction qui ne font que ça: retourner un seul élément.

On pourrait aussi le faire avec une lambda:

>>> getfirst = lambda x: x[0]
>>> getfirst([1, 2, 3])
1

On a le même chose de chose pour les attributs. attrgetter est une fonction qui crée une fonction. La fonction créée retourne toujour la valeur du même attribut.

>>> from operator import attrgetter
>>> gettype = attrgetter('type') # crée une fonction qui ...
>>> gettype(Classe('cm2')) # ... retourne la valeur de l'attribut 'type'
'cm2'
>>> gettype(Classe('6eme'))
'6eme'

On pourrait aussi le faire avec une lambda:

>>> gettype = lambda x: x.type
>>> gettype(Classe('6eme'))
'6eme'

Ces fonctions n'ont pas l'air utile comme ça, mais en Python les fonctions peuvent être passées en paramètres. Cela permet donc de faire des choses intéressante comme de l'injection de dépendance. C'est notamment ainsi que fonctionne key dans sorted().

Bref, jetez un coup d'oeil à operator, il est plein comme un oeuf:

>>> from operator import <tab>
abs               delitem           getslice          ilshift           ior               isSequenceType    le                neg               sequenceIncludes
add               delslice          gt                imod              ipow              is_               lshift            not_              setitem
and_              div               iadd              imul              irepeat           is_not            lt                or_               setslice
attrgetter        eq                iand              index             irshift           isub              methodcaller      pos               sub
concat            floordiv          iconcat           indexOf           isCallable        itemgetter        mod               pow               truediv
contains          ge                idiv              inv               isMappingType     itruediv          mul               repeat            truth
countOf           getitem           ifloordiv         invert            isNumberType      ixor              ne                rshift            xor
]]>
http://sametmax.com/le-module-operator-en-python/feed/ 9