callable – Sam & Max http://sametmax.com Du code, du cul Wed, 23 Dec 2020 13:35:02 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Les trucmuchables en Python http://sametmax.com/les-trucmuchables-en-python/ http://sametmax.com/les-trucmuchables-en-python/#comments Tue, 23 Dec 2014 23:58:42 +0000 http://sametmax.com/?p=13004 Parcourez votre itérable, passez un callable, retournez un indexable…

En Python on aime le duck typing. On ne va donc pas s’intéresser à un type, mais à un comportement.

Quand vous voyez un suffixe “-able” en anglais, ça veut dire “accepte qu’on lui fasse quelque chose”. Par exemple, “fuckable” = “baisable”.

Sur ce morceau de poésie, je vous offre un peu de Vivaldi pour faire glisser profondément ce gros article qui va lister les chosables les plus connus.

Iterable

Le plus important en Python.

Un itérable est ce qui accepte l’itération, ce sur quoi on peut itérer, c’est à dire une collection dont on peut prendre les éléments un à un.

Pour faire simple, tout ce sur quoi on peut appliquer une boucle for.

L’exemple le plus connu sont les listes :

for x in ['une', 'liste']:
    print(x)
une
liste

Mais cela s’applique à bien d’autres types :

for x in 'unechaine':
...     print(x)
...
u
n
e
c
h
a
i
n
e
for x in ('un', 'tuple'):
...     print(x)
...
un
tuple
for x in {'un': 'dico', 'par': 'cle'}:
...     print(x)
...
un
par
for x in set(('un', 'set')):
...     print(x)
...
un
set
with open('/tmp/test', 'w') as f:
    f.write('un\nfichier')
...
for x in open('/tmp/test'):
...     print(x, end="")
...
un
fichier

Les tuples, dicos, sets, fichiers, et strings sont itérables. Beaucoup de structures de données du module collections (deque, namedtupple, defaultdict) sont itérables.

Mais surtout, les générateurs sont itérables :

def generator():
    yield 1
    yield 2

for x in generator():
    print(x)
1
2

Pour vérifier si quelque chose est itérable, on peut utiliser la fonction iter(). Cette fonction prend un iteérable, et retourne un générateur (appelé “iterator”) qui permet d’énumérer chaque élément de l’iterable :

lst = ['ceci', 'est', 'aussi', 'une', 'liste']
generateur = iter(lst)
next(generateur)
'ceci'
next(generateur)
'est'
next(generateur)
'aussi'
next(generateur)
'une'

iter() lève TypeError sur un non iterable :

iter(1)
Traceback (most recent call last):
  File "", line 1, in 
    iter(1)
TypeError: 'int' object is not iterable

Pour la culture, c’est ainsi que la boucle for fonctionne : à coup de next() sur un itérateur.

On peut rendre n’importe quelle objet itérable en définissant la méthode __iter__, qui doit retourner un générateur :

class NouvelIterable:
    def __iter__(self):
        # mettre des yield marche aussi
        return iter([1, 2, 3])

for x in NouvelIterable():
    print(x)
1
2
3

Les itérables sont les bidulables les plus important en Python car de très nombreuses fonctions les acceptent :

list(sorted(('AZERTY'))) # tri
['A', 'E', 'R', 'T', 'Y', 'Z']
list(reversed('AZERTY')) # inversion
['Y', 'T', 'R', 'E', 'Z', 'A']
list(zip('AZERTY', (100, 300, 600))) # lier deux itérables
[('A', 100), ('Z', 300), ('E', 600)]
any(set((1, 0, 1, 0, 1, 1, 2))) # un élément au moins est vrai ?
True
all(set((1, 0, 1, 0, 1, 1, 2))) # tous les éléments sont vrais ?
False

Et elles retournent souvent des itérables également :)

La plupart des itérables sont compatibles entre eux. Y compris les générateurs. Qui souvent traitent eux même des itérables et retournent des itérables. Cela permet de faire d’énormes pipelines de traitements connectés les uns aux autres :

s = '123456789'
res = (int(x) * x for x in s)
tuple(reversed(list(res)))[:4]
('999999999', '88888888', '7777777', '666666')

Mutable

Dont on peut changer la valeur.

Quand on assigne une variable en Python, on ne change pas la valeur de l’objet, on change la valeur de la variable :

a = 1
a = 2

Ici 1 n’a pas changé, la valeur stockée dans a a changé.

C’est différent de ceci :

a = [1]
a[0] = 2
a
[2]

Ici, c’est la même liste qui est dans a, mais la valeur stockée dans la liste a changé.

Cette notion est importante car en Python, les variables ne contiennent en fait pas vraiment des valeurs, mais des références à ces valeurs.

Si je change le contenu de la variable, il n’y a pas d’effet de bords :

a = [1, 2, 3]
b = a # b et a contienne une référence à la même liste
b
[1, 2, 3]
a = [4, 5, 6] # le contenu de a change
b
[1, 2, 3] # b et a ont une contenu différents

Si je change la valeur de ma structure de données, ici ma liste, alors il y a un effet de bord :

a = [1, 2, 3]
b = a
a[0] = 1000
b # a et b référencent la même liste
[1000, 2, 3]

En effet, a ne contient pas la liste, mais une référence à la liste. Quand on copie le contenu de a vers b, on ne copie pas la liste, mais cette référence. Donc a et b sont des variables qui pointent vers la même liste.

Il est alors important de savoir quelles opérations modifient quelque chose, et lesquelles ne les modifient pas.

Les listes, les dictionnaires et les sets sont modifiables, on dit qu’il sont “mutables”.

On peut le voir avec la fonction id() qui renvoie le numéro unique de l’objet :

une_liste = []
id(une_liste)
140693805855368
une_liste.append(1)
une_liste
[1]
id(une_liste)
140693805855368

La liste a changé, mais l’id est le même, c’est le même objet.

Les opérations qui “changent la valeur” sur les types mutables sont performantes en Python car il n’y a pas besoin de recréer un objet à chaque fois.

Les tuples, les nombres, les chaînes de caractères ne sont pas modifiables. Il ne sont pas “mutables” :

id(une_liste)
140693805855368
un_tuple = (1, 2, 3)
id(un_tuple)
140693772746040
un_tuple += (4, 5, 6)
un_tuple
(1, 2, 3, 4, 5, 6)
id(un_tuple)
140693772879144

Le tuple n’a pas changé : l’id n’est pas le même car la variable un_tuple contient un nouvel objet.

Les opérations qui “changent la valeur” sur les types non mutables sont moins performantes en Python car il faut recréer un objet à chaque fois.

Par défaut, toute classe que vous écrivez crée un objet mutable.

Callable

Tout ce qui peut être appelé, c’est à dire qu’on peut mettre () après le nom de la variable qui le contient pour obtenir un effet.

Ce qui vient en premier en tête ce sont les fonctions (ou les méthodes):

def foo():
...     print("Je suis appelée")
...
foo() # j'appelle ma fonction
Je suis appelée

Mais, en Python, le concept d’appeler va plus loin.

Une classe est un callable :

class Bar:
    def __init__(self):
        print("Je suis appelée")
Bar() # j'instancie en utilisant ()

Je suis appelée

Un type est un callable :

set()
set()

Et on peut rendre n’importe quel objet callable en définissant la méthode __call__ :

class UnCallableQuiCreerUnCallable:
    def __call__(self):
        print('Je suis appelé')

callable = UnCallableQuiCreerUnCallable()
callable()
Je suis appelé

Donc quand on vous dit : “ceci attend un callable en paramètre”, vous pouvez passer n’importe quel type de callable. Pas juste une fonction. On peut créer des décorateurs avec et pour n’importe quel callable.

Si on essaye d’appeler un objet qui n’est pas un callabe, on obtient un TypeError :

lst
[1]
lst()
Traceback (most recent call last):
  File "", line 1, in 
    lst()
TypeError: 'list' object is not callable

Hashable

Les clés des dictionnaires n’ont pas besoin d’être des chaînes de caractères. Elles peuvent être n’importe quel objet hashable. Pour les types de base, ce sont les non mutables, soit les strings, mais aussi ints, floats ou tuples :

dico = {('une', 'cle', 'qui', 'est', 'un', 'tuple'): 1}
len(dico)
1
dico[('une', 'cle', 'qui', 'est', 'un', 'tuple')]
1

Pour obtenir cet effet, le dictionnaire prend l’objet passé en clé, et calcule un hash, une empreinte unique de l’objet. Pour que cela marche, il faut que le hash d’un objet donne toujours le même résultat si il est appliqué deux fois au même objet, ou à deux objets parfaitement égaux.

Un objet hashable est donc un objet qu’on peut utiliser comme clé de dictionnaire. C’est un objet qu’on peut passer à la fonction hash(). Dans la stdlib, les types non mutables sont hashable, et les types mutables ne le sont pas :

hash("fdkslmf")
4874978338908949266
hash([])
Traceback (most recent call last):
  File "", line 1, in 
    hash([])
TypeError: unhashable type: 'list'

Mais on peut créer sa propre définition de ce qu’est un objet hashable avec la méthode __hash__, qui doit retourner un entier :

class Personne:
    def __init__(self, nom, prenom, age):
        self.nom = nom
        self.prenom = prenom
        self.age = age
    def __hash__(self):
        return sum((ord(x) for x in (self.nom + self.prenom))) + self.age
...
hash(Personne("bob", "sinclaire", 78))
1339

Vous avez néanmoins intérêt à savoir ce que vous faites en faisant ça, c’est un nid de frelons.

Subscriptables

Ce dont on peut récupérer une partie avec []. Essayer sur un objet qui ne l’est pas peut lever TypeError: 'x' object is not subscriptable ou une sous erreur.

Car on peut utiliser [] de deux façons.

Indexable

Dont on peut récupérer un élément à une position particulière, avec la syntaxe []. Dans la stdlib, les listes, les chaînes de caractères, les tuples et les dictionnaires sont indexables mais pas les sets :

"fdjskl"[0]
'f'
('1', '2')[0]
'1'
{'yo': 'man'}['yo']
'man'
s = set((1, 2))
s[0]
Traceback (most recent call last):
  File "", line 1, in 
    s[0]
TypeError: 'set' object does not support indexing

On peut définir son propre comportement d’indexation avec __getitem__ :

class MainGauche:
    def __getitem__(self, index):
        return "Index de la main gauche"

main = MainGauche()
print(main[0])
Index de la main gauche

Sliceable

Dont on peut récupérer un sous ensemble des éléments avec la syntaxe [start:stop:step]. Un sliceable est souvent indexable, mais l’inverse n’est pas forcément vrai. Dans la stdlib, les listes, les strings et les tuples sont sliceables, mais pas les dictionnaires ni les sets :

"fdjskl"[1::2]
'dsl'
('1', '2', True, False)[:-1]
('1', '2', True)
{'yo': 'man'}[1:2]
Traceback (most recent call last):
  File "", line 1, in 
    {'yo': 'man'}[1:2]
TypeError: unhashable type: 'slice'

Le slice s’implémente comme l’index, avec __getitem__. La différence est qu’au lieu de recevoir une valeur ordinaire, vous allez recevoir un objet slice :

class MainDroite:
    def __getitem__(self, slice):
        print(slice.start, slice.stop)
        return "Slice de la main droite. Heu..."

main = MainDroite()
print(main[2:6])
2 6
Slice de la main droite. Heu...
]]>
http://sametmax.com/les-trucmuchables-en-python/feed/ 9 13004
Qu’est-ce qu’un callable en Python ? http://sametmax.com/quest-ce-quun-callable-en-python/ http://sametmax.com/quest-ce-quun-callable-en-python/#comments Sat, 18 May 2013 07:05:36 +0000 http://sametmax.com/?p=1605 Petit définition du jour.

Un callable, qu’on peut traduire maladroitement par “appelable”, est un objet qui peut être appelé. Et ça ne vous arrange pas beaucoup de savoir ça.

On parle de callable dans les tutos, les docs, etc, en supposant que vous savez ce que ça veut dire.

En fait c’est très simple, si vous pouvez mettre deux parenthèses après le nom d’un objet (en fait de la variable qui contient l’objet, mais ne chipotons pas) pour obtenir un effet, alors l’objet est un callable.

Une fonction est un callable:

>>> def fonction():
...     print("Vous m'avez appelé")
...
>>> fonction() # appel de la fonction
Vous m'avez appelé

Une classe est un callable:

>>> Class() # appel de la classe
<__main__.Class object at 0x26d8310>

Une méthode est un callable :

>>> class Class(object):
def methode(self):
print("Veuillez patientez, nous allons répondre à votre appel")
...
>>> obj = Class()
>>> obj.methode() # appel de la méthode
Veuillez patientez, nous allons répondre à votre appel

Il sont callables car on peut faire () dessus, et il se passe quelque chose. On peut les appeler.

Et par voie de conséquence, un callable peut retourner une valeur quand il est appelé. En fait, un callable est même garanti de retourner une valeur quand il est appelé, puisque toute fonction ou méthode retourne None par défaut en Python, et toute classe retourne au moins un objet vide.

Les string, les int, les listes, les fichiers ou les dicos ne sont pas des callables :

>>> 1()
Traceback (most recent call last):
File "", line 1, in 
1()
TypeError: 'int' object is not callable

>>> ""()
Traceback (most recent call last):
File "", line 1, in 
""()
TypeError: 'unicode' object is not callable

>>> []()
Traceback (most recent call last):
File "", line 1, in 
[]()
TypeError: 'list' object is not callable

>>> {}()
Traceback (most recent call last):
File "", line 1, in 
{}()
TypeError: 'dict' object is not callable

>>> f = open('/etc/fstab')
>>> f()
Traceback (most recent call last):
File "", line 1, in 
f()
TypeError: 'file' object is not callable

Mais les classes (et oui, ce sont des classes, pas des fonctions malgré leurs noms en minuscule) str, int, list, dict et file sont des callables :

>>> int()
0
>>> str()
''
>>> dict()
{}
>>> list()
[]
>>> file('/etc/fstab')

En fait, la plupart des objets ne sont tout simplement pas des callables. Aucun nouvel objet n’est un callable par nature.

>>> obj = Class()

>>> obj()
Traceback (most recent call last):
File "", line 1, in 
obj()
TypeError: 'Class' object is not callable

Donc, quand on vous dit de passer un “callable” en paramètre, généralement on vous dit de passer une fonction (sans les parenthèses), une classe (sans les parenthèses) ou une méthode (sans les parenthèses), afin de s’en servir comme callback.

Par exemple, les champs des modèles Django acceptent un callable comme paramètre par défaut. Comme les callables sont garantis de retourner une valeur, Django utilise cela pour créer dynamiquement une valeur par défaut. La fonction sort() des listes accepte un callable en callback pour le paramètre key.

Demander un callable, c’est donc demander un truc qu’on peut appeler en utilisant des parenthèses, et qui va retourner un autre truc.

Au passage, pour votre culture, on peut créer ses propres callables. Ça sert rarement, dans quelques cas tordus. Il suffit de créer une classe avec la méthode magique __call__, et l’objet retourné sera automatiquement un callable :

>>> class CreerUnCallable(object):
...         def __call__(self):
...                 return "Vous m'avez appelé, maître ?"
...
>>> un_callable = CreerUnCallable()
>>> print(un_callable())
Vous m'avez appelé, maître ?

On a donc ici DEUX callables : la classe, qui est callable par nature, et l’instance de la classe, qui est callable grâce à la méthode __call__.

Et moi j’ai commencé à taper cet article parce que je me suis dit que c’était une notion simple et que ça allait être vite fait, juste avant le petit dèj. Ca fait une heure que je suis dessus. Qu’est-ce que je suis con des fois.

]]>
http://sametmax.com/quest-ce-quun-callable-en-python/feed/ 8 1605