Notions de Python avancées

Découvrez Python plus en profondeur

a marqué ce sujet comme résolu.

Ce qui suit devrait être plus explicite. :)

Instance, classe et métaclasse

Une classe est ainsi une instance de la classe type.

Tu pourrais explicité cela. En effet, si la notion de type est assez intuitive (un nombre n'est pas la même chose qu'une chaîne), le rapport avec les classes (5 est une instance de int, par exemple) est, je pense, moins connu.

Peut-être d'ailleurs pourrais-tu commencer par là, ayant ainsi le raisonnement suivant :

  • En Python, tous les objets ont un type
  • Exemples simples (entiers, chaînes, etc.), en utilisant juste la fonction type
  • Mais, pour un objet, être du type T revient à être une instance de cette classe T
  • Or une classe est un objet comme un autre
  • Donc quelle est la classe d'une classe (son type) ?

D'ailleurs, y a-t-il une différence entre "être du type T" et "hériter de la classe T" ? Quid des héritages multiples ?

Quel est donc ce type ?

Une chaîne de caractères représentant le nom de la classe ;

Je ne comprends pas tellement l'intérêt vu qu'on stocke la classe dans une variable : A = type(...).

Le dictionnaire des attributs et méthodes de la classe.

Peut-on renseigner des attributs et méthodes de classes ou statiques ?

Le vrai constructeur

Nous choisissons ici de faire appel à object.new dans notre constructeur (via super), mais nous n'y sommes pas obligés.

Peut-être cela a-t-il plus sa place dans un tutoriel sur la POO, mais aurais-tu un exemple concret où MaClasse.__new__ ne fait pas appel à object.__new__ ?

Les métaclasses

Puisque nous voulons altérer la création et non l'initialisation, c'est dans le constructeur que le tout va s'opérer.

J'ai un peu bloqué là-dessus, puisque je me suis dit que ce n'est pas la création de la métaclasse qu'on souhaite altérer, mais celle de la classe. Du coup, je ne comprends pas tellement pourquoi on modifie le constructeur de la métaclasse.

>>> class M(type):

Tu pourrais faire autre chose dans le __new__, même si c'est bidon. Là, M ne sert à rien d'autre que montrer la syntaxe. Par exemple, Sam illustre ça en créant une métaclasse qui préfixe tous les noms d'attributs et de méthodes.

Cette méthode doit toutefois retourner un dictionnaire ou objet similaire.

Par "objet similaire", tu veux dire "objet indexable (par des chaînes ?)" ?

Les énumérations en Python sont implémentées à l'aide de métaclasses.

Peut-être pourrais-tu redonner le lien fourni dans la première section ?

Une implémentation simplifiée possible d'Enum est la suivante :

J'ai vraiment du mal avec cet exemple. N'y aurait-il pas moyen de le simplifier en enlevant le cache ?

Utiliser une fonction comme métaclasse

Ce qui fait qu'à l'héritage, l'appel à la métaclasse serait perdu

J'ai un peu bloqué là-dessus parce que tu n'as pas vraiment mis en relation l'héritage et les métaclasses.

TP : Évaluation paresseuse

Et dans notre exemple, l'objet retourné devra posséder les méthodes add et eq. Méthodes qui se chargeront d'effectuer le calcul du carré.

Ce ne sont ici que deux opérateurs, mais il en existe beaucoup d'autres, dont l'énumération serait inutile et fastidieuse, et il va nous falloir tous les gérer.

A la première lecture, j'ai eu un peu de mal là-dessus. Peut-être pourrais-tu clarifier en disant explicitement qu'on souhaite exécuter la fonction (calculer le carré) lorsqu'on applique un opérateur sur notre objet, ou plus généralement, qu'on souhaite exécuter l'expression lorsqu'on appelle une méthode de l'objet.

Merci.

+0 -0

Merci pour les conseils sur l'introduction des métaclasses, je vais voir ce que je peux faire.

D'ailleurs, y a-t-il une différence entre "être du type T" et "hériter de la classe T" ? Quid des héritages multiples ?

Vayel

Une grosse différence, oui. Seule une classe peut hériter d'une autre.

1
class A(int): pass
  • int est de type type. A est aussi de type type.
  • 5 (ou int(5)) est de type int. A(5) est de type A.
  • A hérite d'int et d'object. int hérite d'object.
  • 5 et A(5) n'héritent de rien, ce ne sont pas des classes.

Je ne comprends pas tellement l'intérêt vu qu'on stocke la classe dans une variable : A = type(...).

Vayel

C'est surtout utile pour pour l'affichage (repr utilise le nom de la classe), mais aussi simplement pour assigner une valeur au __name__ de la classe. Car quand tu la construis, tu n'as aucune idée du nom de l'étiquette à laquelle tu vas associer la classe.

Peut-on renseigner des attributs et méthodes de classes ou statiques ?

Vayel

Je n'ai pas compris la question.

Peut-être cela a-t-il plus sa place dans un tutoriel sur la POO, mais aurais-tu un exemple concret où MaClasse.__new__ ne fait pas appel à object.__new__ ?

Vayel

Je peux te fournir un exemple ici, mais je ne souhaite pas le faire dans le cours, ça n'est absolument pas une pratique conseillée, puisque ça reviendrait à renvoyer une valeur d'un autre type.

1
2
3
class A:
    def __new__(cls, value):
      return str(value)

J'ai un peu bloqué là-dessus, puisque je me suis dit que ce n'est pas la création de la métaclasse qu'on souhaite altérer, mais celle de la classe. Du coup, je ne comprends pas tellement pourquoi on modifie le constructeur de la métaclasse.

Vayel

Oui, on souhaite altérer la création de la classe, pas de ses instances. Il faut donc travailler dans la méthode __new__ de la métaclasse. Quand on parle du constructeur d'une classe, il s'agit de la méthode qui construit les instances de cette classe. Donc la méthode qui construit notre classe est la méthode qui construit les instances de notre métaclasse.

Tu pourrais faire autre chose dans le __new__, même si c'est bidon. Là, M ne sert à rien d'autre que montrer la syntaxe. Par exemple, Sam illustre ça en créant une métaclasse qui préfixe tous les noms d'attributs et de méthodes.

Vayel

Je n'aime pas cet exemple de sam&max, car il n'a justement aucune utilité. Je préfère montrer les métaclasses quand elles sont utiles. L'exemple a ce niveau-là dans le cours a en effet juste pour but de présenter la syntaxe.

Par "objet similaire", tu veux dire "objet indexable (par des chaînes ?)" ?

Vayel

Je veux dire un objet similaire à un dictionnaire, avec le même comportement (OrderedDict par exemple).

Peut-être pourrais-tu redonner le lien fourni dans la première section ?

Vayel

Le lien Wikipédia ? Pour quoi faire ?

J'ai vraiment du mal avec cet exemple. N'y aurait-il pas moyen de le simplifier en enlevant le cache ?

Vayel

Sans le cache, on ne répond plus à l'égalité Color(1) is Color.red, puis il ne s'agit que de 3 lignes dans le code.

J'ai un peu bloqué là-dessus parce que tu n'as pas vraiment mis en relation l'héritage et les métaclasses.

Vayel

Parce qu'il n'y a pas de relation entre les deux.

A la première lecture, j'ai eu un peu de mal là-dessus. Peut-être pourrais-tu clarifier en disant explicitement qu'on souhaite exécuter la fonction (calculer le carré) lorsqu'on applique un opérateur sur notre objet, ou plus généralement, qu'on souhaite exécuter l'expression lorsqu'on appelle une méthode de l'objet.

Vayel

Il me semble que c'est ce qui est décrit au-dessus.

Une grosse différence, oui. Seule une classe peut hériter d'une autre.

Effectivement, pardon. Du coup, je reformule : y a-t-il une différence entre "être de type T" et "être une instance de la classe T" ?

Je n'ai pas compris la question.

Quel serait mon appel à type pour créer la classe suivante ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A:
  attr = 3

  @staticmethod
  def f(): pass

  @classmethod
  def g(cls): pass

  def __init__(self):
    self.x = 42

  def h(self, n): pass

puisque ça reviendrait à renvoyer une valeur d'un autre type

Tu pourrais le préciser, dire que pour instancier A, il faut nécessairement passer par object.__new__(A, ...).

Le lien Wikipédia ? Pour quoi faire ?

Parce que si on ne comprend pas ce qu'est une énumération, ce n'est même pas la peine de lire le code (j'ai essayé…). Du coup, s'il faut mettre un lien à ce sujet, je pense que c'est à ce niveau. :)

Parce qu'il n'y a pas de relation entre les deux.

Du coup, je ne suis pas sûr de comprendre "Ce qui fait qu'à l'héritage, l'appel à la métaclasse serait perdu (cet appel n'étant réalisé qu'une fois).".

Il me semble que c'est ce qui est décrit au-dessus.

Ouep, pas faux. Je chipote, mais parler d'appel aux méthodes (possiblement spéciales) au niveau de "mais ne réaliser l'appel qu'au moment où nous avons besoin du résultat." me semble une bonne idée. Sinon, on a (j'ai eu) un peu de mal à comprendre par la suite pourquoi on liste toutes les méthodes spéciales.

Merci pour tes explications.

+0 -0

Effectivement, pardon. Du coup, je reformule : y a-t-il une différence entre "être de type T" et "être une instance de la classe T" ?

Vayel

Du coup il n'y a qu'une légère différence (et encore, cela va dépendre de ta conception des choses). être une instance de T (isinstance(obj, T)), ça signifie être de type T, ou d'un type héritant de T.

Quel serait mon appel à type pour créer la classe suivante ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class A:
  attr = 3

  @staticmethod
  def f(): pass

  @classmethod
  def g(cls): pass

  def __init__(self):
    self.x = 42

  def h(self, n): pass

Vayel

Quelque chose comme ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@staticmethod
def A_f(): pass

@classmethod
def A_g(cls): pass

def A___init__(self):
  self.x = 42

def A_h(self, n): pass

A = type('A', (), {'attr': 3, 'f': A_f, 'g': A_g, '__init__': A___init__, 'h': A_h})

Ou encore :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def A_f(): pass

def A_g(cls): pass

def A___init__(self):
  self.x = 42

def A_h(self, n): pass

A = type('A', (), {'attr': 3, 'f': staticmethod(A_f), 'g': classmethod(A_g), '__init__': A___init__, 'h': A_h})

Tu pourrais le préciser, dire que pour instancier A, il faut nécessairement passer par object.__new__(A, ...).

Vayel

Je vais voir.

Parce que si on ne comprend pas ce qu'est une énumération, ce n'est même pas la peine de lire le code (j'ai essayé…). Du coup, s'il faut mettre un lien à ce sujet, je pense que c'est à ce niveau. :)

Vayel

Mais il est justement donné plus haut, quand le terme « énumération » est introduit.

Du coup, je ne suis pas sûr de comprendre "Ce qui fait qu'à l'héritage, l'appel à la métaclasse serait perdu (cet appel n'étant réalisé qu'une fois).".

Vayel

Parce que la métaclasse dans ce cas n'est pas le type de la classe.

Quand on fait

1
class B(A): pass

cela revient à faire

1
B = type(A)('B', (A,), {})

Mais si A est en réalité instanciée via une fonction faisant appel à type, A est alors une instance directe de type, et type(A) vaudra type. Donc en héritant de A, la fonction ayant servi de métaclasse ne sera pas utilisée, puisque sa référence a été perdue.

Ouep, pas faux. Je chipote, mais parler d'appel aux méthodes (possiblement spéciales) au niveau de "mais ne réaliser l'appel qu'au moment où nous avons besoin du résultat." me semble une bonne idée. Sinon, on a (j'ai eu) un peu de mal à comprendre par la suite pourquoi on liste toutes les méthodes spéciales.

Vayel

Et moi j'ai du mal à comprendre le problème. Tout ce que l'on souhaite, c'est que notre objet paresseux se comporte comme le vrai résultat.

Mais il est justement donné plus haut, quand le terme « énumération » est introduit.

Ouep, mais plus haut, on n'a pas de raison particulière de le consulter. Ce qui fait que lorsqu'on arrive au code, il faut remonter pour lire l'article. Autant le mettre plus bas, non ? :)

Parce que la métaclasse dans ce cas n'est pas le type de la classe.

Du coup, il y a quand même un petit rapport entre métaclasse et héritage, vu qu'une métaclasse induit un type, lequel se transmet par héritage, non ? J'ignore pourquoi, mais un moment j'ai cru que la métaclasse de A s'appliquait également à ses filles.

Mais si A est en réalité instanciée via une fonction faisant appel à type, A est alors une instance directe de type, et type(A) vaudra type. Donc en héritant de A, la fonction ayant servi de métaclasse ne sera pas utilisée, puisque sa référence a été perdue.

Merci, je comprends mieux.

Et moi j'ai du mal à comprendre le problème. Tout ce que l'on souhaite, c'est que notre objet paresseux se comporte comme le vrai résultat.

Ce n'est pas très grave, ça se comprend bien comme ça.

+0 -0

Du coup, il y a quand même un petit rapport entre métaclasse et héritage, vu qu'une métaclasse induit un type, lequel se transmet par héritage, non ? J'ignore pourquoi, mais un moment j'ai cru que la métaclasse de A s'appliquait également à ses filles.

Vayel

Elle s'applique à ses filles, oui.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
>>> class M(type): pass
... 
>>> class A(metaclass=M): pass
... 
>>> type(A) # M est bien la métaclasse de A
<class '__main__.M'>
>>> class B(A): pass
... 
>>> type(B) # Ainsi que de B
<class '__main__.M'>
>>> def M2(*args): return type(*args) # M2 est une "fausse" métaclasse
... 
>>> class A2(metaclass=M2): pass
... 
>>> type(A2) # M2 n'est pas un type, donc n'est pas la métaclasse de A2
<class 'type'>
>>> class B2(A2): pass
... 
>>> type(B2) # Ni de B2
<class 'type'>

Le type se propage effectivement, mais si je reprends la classe de Sam où il renomme les méthodes, seules celles de A le seront (et celles de B héritées de A), non ? Autrement dit, les lignes suivantes n'ont pas le même effet :

1
2
3
4
>>> class B(A): pass
...
>>> class B(A, metaclass=M): pass
...

D'ailleurs, à quel moment intervient l'héritage ? Quel dictionnaire de méthodes sera passé à M (y aura-t-il celles héritées de A) ?

+0 -0

Et c'est en partie pour ça que l'exemple de Sam est mauvais. prefixer est une fonction utilisée comme métaclasse, mais n'est pas un type. Ainsi, le type de Ronflex est type et non prefixer. Quand on hérite de Ronflex, on conserve le même type (type), et c'est donc le constructeur de type qui sera utilisé pour les classes filles.

Dans ton exemple, si A possède comme « métaclasse » M, et que M n'est pas un type, en effet, tes deux lignes n'auront pas le même effet.

Il faut voir l'héritage en deux temps : lors de la création d'une classe, ses classes parentes sont listées pour former le MRO (mais aucun appel à ces classes mères n'est réalisé). Ce même MRO qui sera utilisé pour résoudre les accès aux attributs/méthodes de notre classe fille (et c'est à ce moment là que l'on fera appel aux mères, pas avant).

Le constructeur de la métaclasse prend en paramètre dict le corps de la classe que l'on définit, ce corps ne comprend pas les méthodes des classes mères.

Dans ton exemple, si A possède comme « métaclasse » M, et que M n'est pas un type, en effet, tes deux lignes n'auront pas le même effet.

En fait, je pensais qu'une métaclasse ne s'appliquait pas aux classes filles. Par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class M(type):
    def __new__(cls, name, bases, dict):
        dict = {
            **{k: v for k, v in dict.items() if '__' in k},
            **{(k + '_'): v for k, v in dict.items() if not '__' in k}
        }

        print(dict)
        return super().__new__(cls, name, bases, dict)

class A(metaclass=M):
    def meth_mother(self):
        pass

class B(A):
    def meth_child(self):
        pass

affiche :

1
2
{'__qualname__': 'A', 'meth_mother_': <function A.meth_mother at 0x7fdc58462378>, '__module__': '__main__'}
{'__qualname__': 'B', '__module__': '__main__', 'meth_child_': <function B.meth_child at 0x7fdc58462400>}

alors que j'imaginais avoir :

1
2
{'__qualname__': 'A', 'meth_mother_': <function A.meth_mother at 0x7fdc58462378>, '__module__': '__main__'}
{'__qualname__': 'B', '__module__': '__main__', 'meth_child': <function B.meth_child at 0x7fdc58462400>}

Est-il possible de reproduire le deuxième comportement (les méthodes de B non renommées) ?

+0 -0

Aucune idée, n'ayant pas encore une idée précise des applications possibles des métaclasses. Bon, je vais laisser ça murir un peu, et reprends le tutoriel du début :

Introduction

Le pré-requis pour suivre ce tutoriel est de connaître Python, même à un niveau débutant, il est simplement nécessaire de savoir manipuler les types de base (nombres, chaînes de caractères, listes, dictionnaires). Connaître le mécanisme des exceptions est un plus.

J'aurais personnellement mis des bases en OO en pré-requis. Ne serait-ce que pour connaître les termes "attribut" et "méthode".

Conteneurs

Introduction

Ce nom barbare regroupe tous les objets sur lesquels l'opérateur [] peut être utilisé.

C'est-à-dire qu'on peut sélectionner un élément particulier ?

Une autre catégorie importante de conteneurs est formée par les mappings : il s'agit des objets qui associent des valeurs à des clefs, comme le font les dictionnaires.

Je ne comprends pas tellement le "Une autre catégorie", puisqu'on peut utiliser [] sur un dictionnaire.

Objets indexables

Nous allons ici nous contenter de faire un proxy autour d'une liste existante

Tu pourrais expliquer brièvement ce qu'est un proxy.

Itérables

Introduction

Les types str, tuple, list, dict et set sont d'autres itérables bien connus. Un grand nombre d'outils Python que nous verrons par la suite travaillent avec des itérables, il est donc intéressant d'en tirer profit.

Existe-t-il des conteneurs non itérables dans la bibliothèque standard ? Ailleurs ?

Callables

Introduction

Appeler un objet consiste à utiliser l'opérateur (), en lui passant un certain nombre d'arguments

>>> print('Hello', 'world', end='!\n') # Appel d'une fonction avec différents paramètres

Tu as un coup "argument", un coup "paramètre".

Paramètres de fonctions

il s'agit ici d'un opérateur unaire : c'est à dire qu'il n'opère que sur un objet, en se plaçant devant.

Est-ce le cas de tous les opérateurs unaires ?

La seule règle est encore une fois de placer * après tous les arguments positionnels, et ** après tous les nommés.

Plus depuis Python 3.5.

Call-me maybe

Comme pour les itérables, c'est un objet qui possède une méthode call

Je chipote, mais les itérables n'ont pas nécessairement de méthode __call__. Tu pourrais dire : "Comme pour les itérables, c'est un objet qui possède une méthode spéciale, call, …".

Utilisation des callables

>>> list(map(lambda p: p[0], points))

Je ne suis pas sûr que ce soit une bonne idée de donner cet exemple, puisque procéder de la manière suivante me semble préférable :

1
>>> [p[0] for p in points]

Il doit même exister une builtin pour ça. Calculer la liste des distances à l'origine serait peut-être plus judicieux.

Modules operator et functools

l'application partielle de la fonction nous créera un nouveau callable qui, quand il sera appelé avec de nouveaux arguments, nous renverra le résultat de la première fonction avec l'ensemble des arguments.

Ce paragraphe n'est pas très clair. Tu pourrais prendre un cas particulier : une fonction de trois paramètres, on souhaite fixer le premier donc on retourne un callable de deux paramètres qui, quand on l'appelle, retourne…

Peut-être pourrais-tu, pour la culture, faire le rapport avec les fonctions en programmation fonctionnelle (x -> y -> z = x -> (y -> z)) ?

Bien sûr, en pratique, sum est déjà là pour répondre à ce problème.

N'aurais-tu pas un exemple pas trop compliqué où reduce est vraiment utile ?

Tu pourrais de plus ajouter une petite phrase pour inciter le lecteur à recoder ces fonctions par lui-même.

Générateurs

Je n'ai rien à relever, à part peut-être le fait que l'introduction est un peu courte. J'ignore si c'est possible, mais il me semble intéressant de décrire un peu les générateurs, leur utilité.

+0 -0

Décorateurs

D&CO, une semaine pour tout changer

En Python, vous en avez peut-être déjà croisé

s

Nous pouvons réalier un décorateur

réaliser

Envelopper une fonction

Alors, que voit-on ? Pas grand chose.

Comme le lecteur devra reprendre le code pour voir le résultat de help, tu pourrais ne pas le mettre en mode console, qu'il soit plus simple de le récupérer.

Les annotations sont utilisabes

utilisables

TP : divers décorateurs

Nous avons déjà parlé de functools. à

Petit point en trop.

Fonctions génériques

Aurais-tu un exemple concret ? Là, on comprend tout à fait ce dont il est question, mais un exemple d'application serait le bienvenu.

Lors d'appels récursifs, les fonctions parentes restent présentes dans la pile, car n'ont pas terminé leur exécution.

Ca fait bizarre de parler de fonctions parentes, vu qu'il s'agit de la même fonction. Est-ce un terme universel ?

return factorial1(n - 1, acc * n)

factorial2 plutôt. :)


Je me demandais s'il ne pouvait pas être intéressant de proposer des exercices en fin de section/chapitre, en plus du TP. Par exemple, les décorateurs paramétrés sont un peu complexes de prime abord, et ce serait bien de les faire manipuler au lecteur pour qu'il les assimile. nohar le fait ici.

A plus !

+0 -0

Bon, je réponds en coup de vent, merci pour les retours.

J'aurais personnellement mis des bases en OO en pré-requis. Ne serait-ce que pour connaître les termes "attribut" et "méthode".

Vayel

Les bases de l'objet sont pour moi incluses dans les connaissances par défaut de Python.

C'est-à-dire qu'on peut sélectionner un élément particulier ?

Vayel

Par exemple, mais l'opérateur [] permet un peu plus que cela (accès en lecture, écriture et suppression), je ne vois pas où tu veux en venir.

Je ne comprends pas tellement le "Une autre catégorie", puisqu'on peut utiliser [] sur un dictionnaire.

Vayel

Personne ne dit qu'être dans la première catégorie exclue d'être dans la seconde.

Tu pourrais expliquer brièvement ce qu'est un proxy.

Vayel

J'ajouterai ça, oui.

Existe-t-il des conteneurs non itérables dans la bibliothèque standard ? Ailleurs ?

Vayel

Je ne sais pas, pas à ma connaissance, pourquoi ?

Tu as un coup "argument", un coup "paramètre".

Vayel

En effet.

Est-ce le cas de tous les opérateurs unaires ?

Vayel

C'est le cas en Python et dans différents langages, oui (!/not, -, ˜).

Plus depuis Python 3.5.

Vayel

J'ajouterai cette précision.

Je chipote, mais les itérables n'ont pas nécessairement de méthode __call__. Tu pourrais dire : "Comme pour les itérables, c'est un objet qui possède une méthode spéciale, call, …".

Vayel

C'est peut-être en effet mal formulé.

Je ne suis pas sûr que ce soit une bonne idée de donner cet exemple, puisque procéder de la manière suivante me semble préférable :

1
>>> [p[0] for p in points]

Il doit même exister une builtin pour ça. Calculer la liste des distances à l'origine serait peut-être plus judicieux.

Vayel

Et calculer une distance sera probablement aussi plus intuitif avec une liste en intension qu'avec map.

Ce paragraphe n'est pas très clair. Tu pourrais prendre un cas particulier : une fonction de trois paramètres, on souhaite fixer le premier donc on retourne un callable de deux paramètres qui, quand on l'appelle, retourne…

Peut-être pourrais-tu, pour la culture, faire le rapport avec les fonctions en programmation fonctionnelle (x -> y -> z = x -> (y -> z)) ?

Vayel

Je vais y réfléchir.

N'aurais-tu pas un exemple pas trop compliqué où reduce est vraiment utile ?

Vayel

Un reduce est complexe à relire et je déconseille de l'utiliser à moins de savoir très bien ce que l'on fait. Ça pourrait s'utiliser pour le calcul d'une factorielle.

Tu pourrais de plus ajouter une petite phrase pour inciter le lecteur à recoder ces fonctions par lui-même.

Vayel

C'est au lecteur d'en décider. Je lui présente différents concepts mais je ne le prends pas par la main.

Je n'ai rien à relever, à part peut-être le fait que l'introduction est un peu courte. J'ignore si c'est possible, mais il me semble intéressant de décrire un peu les générateurs, leur utilité.

Vayel

Je ne sais pas, c'est à voir.

s

Vayel

Pour l'accord du participe avec « en » ? Non.

Comme le lecteur devra reprendre le code pour voir le résultat de help, tu pourrais ne pas le mettre en mode console, qu'il soit plus simple de le récupérer.

Vayel

Quel est le mal à ce qu'il doive taper les commandes ?

Aurais-tu un exemple concret ? Là, on comprend tout à fait ce dont il est question, mais un exemple d'application serait le bienvenu.

Vayel

Je n'ai pas ça en tête.

Ca fait bizarre de parler de fonctions parentes, vu qu'il s'agit de la même fonction. Est-ce un terme universel ?

Vayel

Je ne sais pas, c'est un terme que j'ai souvent utilisé, et qui ne me paraît pas inapproprié.

Je me demandais s'il ne pouvait pas être intéressant de proposer des exercices en fin de section/chapitre, en plus du TP. Par exemple, les décorateurs paramétrés sont un peu complexes de prime abord, et ce serait bien de les faire manipuler au lecteur pour qu'il les assimile. nohar le fait ici.

Vayel

Peut-être plutôt prévoir une annexe proposant des exercices pour revenir sur les différentes notions, et des exercices pour mixer ces notions. Mais je n'ai pas envie d'encombrer le cours avec ça.

Par exemple, mais l'opérateur [] permet un peu plus que cela (accès en lecture, écriture et suppression), je ne vois pas où tu veux en venir.

Juste expliciter ce que signifie "l'opérateur [] peut être utilisé", comme tu l'as fait pour les callables : "Appeler un objet consiste à utiliser l'opérateur (), en lui passant un certain nombre d'arguments, de façon à recevoir une valeur de retour.".

Existe-t-il des conteneurs non itérables dans la bibliothèque standard ? Ailleurs ?

Je ne sais pas, pas à ma connaissance, pourquoi ?

Simple curiosité.

s

Vayel

Pour l'accord du participe avec « en » ? Non.

Ah ? Chez les étudiants, vous en avez peut-être déjà faites, les pâtes sont un plat courant.

Quel est le mal à ce qu'il doive taper les commandes ?

C'est juste pas pratique, mais pas insurmontable non plus.

+0 -0

Juste expliciter ce que signifie "l'opérateur [] peut être utilisé", comme tu l'as fait pour les callables : "Appeler un objet consiste à utiliser l'opérateur (), en lui passant un certain nombre d'arguments, de façon à recevoir une valeur de retour.".

Vayel

Je veux quand même rappeler que le lecteur connaît un minimum Python, il a donc déjà utilisé cet opérateur.

Ah ? Chez les étudiants, vous en avez peut-être déjà faites, les pâtes sont un plat courant.

Vayel

Justement, cette phrase est fausse, puisque « en » n'a ni genre ni nombre.

C'est juste pas pratique, mais pas insurmontable non plus.

Vayel

Alors je ne comprends pas. Tu me reproches plus haut de ne pas inciter le lecteur à recopier les commandes, et dans ce cas là ça pose problème.

Justement, cette phrase est fausse, puisque « en » n'a ni genre ni nombre.

Merci pour l'information.

Alors je ne comprends pas. Tu me reproches plus haut de ne pas inciter le lecteur à recopier les commandes, et dans ce cas là ça pose problème.

Je me suis mal exprimé alors. Plus haut, je proposais d'inciter le lecteur à recoder les fontions de la bibliothèque standard (itemgetter, methodcaller, etc.), pas celles d'exemple que tu donnes.

+0 -0

Que vois-je ? Que vois-je ?
Du chamboulement dans l’air.

Que vois-je ? Que vois-je ?
Des chapitres supplémentaires.

Que vois-je ? Que vois-je ?
Un nouveau découpage.

Je suis de retour !


Bon, comme vous l’aurez compris, j’ai repris la rédaction de ce cours. Avec le temps, des points m’avaient été rapportés, et j’avais envie d’ajouter d’autres notions au cours.

Au programme :

  • Un redécoupage des chapitres en parties ;
  • Un chapitre dédié aux mutables et aux hashables ;
  • Un chapitre sur les annotations et les signatures ;
  • Un chapitre sur les classes abstraites ;
  • Un cours adapté à Python 3.5 et 3.6 (unpacking étendu, __init_subclass__, __set_name__) ;
  • Une description du module collections ;

À venir :

  • Nouveaux TP ;
  • Nouvelle partie dédiée aux exercices ;

Voilà, le travail n’est donc pas terminé, il me reste surtout à retravailler les TP, pour essayer d’en faire un ensemble plus cohérent, et plus facile d’accès. J’attends vos remarques ;)

Je signale aussi que j’ai prévu d’ajouter divers liens vers certaines références (documentation, sources, autres tutoriaux), et rédige sans connexion Internet et sans connaissance des URL exactes. Pas besoin donc de relever les liens vides, je m’en occuperai vers la fin.

Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l’adresse suivante :

Merci d’avance pour vos commentaires.


Presque un mois a passé depuis la précédente mise à jour. Je n’ai pas chômé mais n’ai pas non plus pensé à venir mettre à jour le contenu ici.

Changements apportés par cette nouvelle version :

  • Ajout des derniers paragraphes manquants à certains chapitres :
    • Méthode replace des signatures ;
    • Méthodes throw et close des générateurs ;
    • __subclasshook__ et NotImplemented ;
  • Réécriture/ajout des TPs de fin de chapitres (annotations/signatures, décorateurs, générateurs, types, métaclasses, classes abstraites) ;
  • Ajout de la section exercices, encore brouillonne, reprenant les TPs supprimés des chapitres précédents.

Bonjour les agrumes !

La bêta a été mise à jour et décante sa pulpe à l’adresse suivante :

Merci d’avance pour vos commentaires.


Encore quelques semaines ont passé, mais j’ai atteint la fin du processus de rédaction. Je vais maintenant pouvoir me consacrer à la relecture ( :( ) et aux corrections ( :colere2: ).

Puisque la rédaction est maintenant terminée, je peux vous fournir un récapitulatif complet par rapport à la version actuellement publiée.

Commençons par quelque chiffres :

  • D’un PDF de 79 pages, on passe maintenant à 175 ;
  • 3431 lignes totalisées auparavant, 7019 aujourd’hui ;
  • Quant au nombre de mots, il évolue de 19387 à 37056 ;
  • Je vous épargne le nombre d’octets, c’est pas très pertinent.

On est donc sur un contenu deux fois plus gros !

Pour ce qui est des changements, maintenant :

  • Je passe d’un format moyen-tuto de 8 chapitres à un big-tuto de 4 parties de 3 chapitres chacune (+ une partie exercices) ;
    • Les chapitres sont ainsi donc regroupés par thèmes (interfaces de bases, fonctions, interfaces avancées, classes)
  • Les TPs présents à la fin des chapitres sont simplifiés, et réduits au nombre d’un par chapitre ;
    • Les TPs excédentaires ne sont pas perdus mais déplacés dans la section exercice ;)
  • 3 nouveaux chapitres sont ajoutés :
    • Objects mutables et hashables – mutabilité, égalité, identité, fonction hash ;
    • Annotations et signatures – annotations, module typing, module inspect, signatures ;
    • Classes abstraites – module abc, opérateurs isinstance, issbuclass et leurs surcharges, collections abstraites (collections.abc) ;
  • Des sections sont ajoutées à d’autres chapitres :
    • Description du module collections dans le chapitre Conteneurs ;
    • Unpacking dans le chapitre Itérables, expliquant cette opération et l’opérateur splat (*). Seul un paragraphe perdu dans le chapitre Callables parlait actuellement de l’unpacking ;
    • Méthodes throw et close dans le chapitre Générateurs ;
    • Réutilisabilité et réentrance des Gestionnaires de contexte ;
  • Division de l’ancien chapitre Métaclasses en deux nouveaux :
    • Types – classes, constructeurs, sous-classes ;
    • Métaclasses – métaclasses, syntaxe et utilité ;
  • Ajout de précisions sur les apports de la version 3.5, et les nouveautés de la 3.6 (__set_name__ pour les descripteurs, __init_subclass__ pour les classes) ;
  • Nouveaux TPs : égalité entre listes, arguments positional-only, map, types immutables, reconnaissance d’interfaces ;
  • Nouveaux exercices : property en version complète, contextmanager, suppress.
  • Ajouts de divers liens et références en fins de chapitres ;
  • Quelques corrections (phrases maladroites sur les générateurs et sur les paramètres à valeur par défaut).

Il me semble être assez exhaustif dans ces commentaires, donc il ne me reste qu’à vous dire une chose… Bonne lecture !

Cool ! J’ai commencé à relire deux-trois trucs :

Module collections

Tu expliques très bien comment utiliser les conteneurs mais pas quand le faire. J’ignore si c’est possible, mais il me semble intéressant de fournir un cas d’utilisation typique pour chaque.

Objets mutables et hashables

Égalité et identité

Deux valeurs sont identiques lorsqu’elles sont une même instance

Un mot sur comment ça se traduit en mémoire ?

L’opérateur is s’utilise principalement avec None. None est une valeur unique (singleton), il n’en existe qu’une instance. Quand on compare une valeur avec None, on vérifie qu’elle est None et non qu’elle vaut None.

J’ai toujours eu du mal avec ça. Si None est un singleton, l’égalité est équivalente à l’identité, non ? Qu’apporte donc l’utilisation de is par rapport à == ?

Hashables

Le condensat

Un mot sur la théorique irréversibilité d’une fonction de hashage ? Le lecteur pourrait se poser la question pour compresser ses données.

Deux valeurs différentes pourront alors avoir un même condensat, c’est ce que l’on appelle un conflit.

Le terme "collision" n’est-il pas plus répandu ?

En Python, on peut calculer le condensat d’un objet à l’aide de la fonction hash.

Tu pourrais indiquer quelle est la fonction de hashage par défaut.

On a vu que le hash était invariable, mais devait correspondre à la valeur.

Je ne comprends pas le "mais". Plutôt "et devrait" ?

Pour les objets mutables, le hash n’est possible que si la modification n’altère pas l’égalité entre deux objets.

Edit : en fait tu en parles plus bas. Peut-être serait-il judicieux d’inverser les parties "À quoi servent-ils ?" et "Implémentation", pour que le lecteur ait sa réponse plus rapidement.

Comment Python distingue-t-il les deux situations suivantes ? Arrive-t-il à détecter la surchage de __eq__ ? Comment sait-il si mon implémentation de cette méthode dépend de l’état de l’objet ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> class A:
...      def __init__(self):
...          self.x = 1
...      def __eq__(self, other):
...          return self.x == other.x
... 
>>> a = A()
>>> b = A()
>>> a == b
True
>>> hash(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'A'
>>> class A: pass
...  
>>> a = A()
>>> b = A()
>>> a == b
False
>>> hash(a) # Là, ça fonctionne
-9223363244493744843

À quoi servent-ils ?

Dans cette partie, tu ne parles que des dictionnaires, mais j’imagine qu’il y a d’autres applications. A défaut de toutes les mentionner, il me semble judicieux d’en présenter au moins une autre classique ou, au minimum, d’en citer l’existence (i.e. d’indiquer que les hashes ne servent pas qu’aux dictionnaires).

Merci.

+0 -0

Merci pour tes commentaires.

J’ai de mon côté commencé une relecture et commencé à corriger quelques fautes et mauvaises formulations.

Tu expliques très bien comment utiliser les conteneurs mais pas quand le faire. J’ignore si c’est possible, mais il me semble intéressant de fournir un cas d’utilisation typique pour chaque.

Vayel

En fait, j’hésitais déjà à présenter le module, me demandant s’il était vraiment à sa place dans le cours, ce dernier ayant surtout vocation à montrer comment implémenter ses structures que documenter l’existant.

Je vais voir ce que je peux faire, mais je n’ai pas envie d’alourdir trop cette section, alors qu’elle n’est à la base qu’une annexe du chapitre Conteneurs.

Un mot sur comment ça se traduit en mémoire ?

Vayel

Je pourrais essayer d’en toucher un mot, oui. Mais c’est souvent délicat de parler de Python et de la mémoire.

J’ai toujours eu du mal avec ça. Si None est un singleton, l’égalité est équivalente à l’identité, non ? Qu’apporte donc l’utilisation de is par rapport à == ?

Vayel

Ce que je vais dire là est à prendre avec des pincettes, je ne suis pas allé voir dans les sources ce qu’il en était réellement.

Je dirais que c’est surtout qu’il est inutile d’utiliser == alors que is fait le travail, plus simplement. is n’a qu’à comparer les adresses des deux objets là où == doit localiser la méthode __eq__ puis l’appeler, et c’est celle-ci qui utilisera is en dernier lieu. Le résultat serait le même, mais ça évite des appels inutiles.

Et je ne sais pas s’il n’y a pas quelque part des objets qui seraient égaux à None, sans être None.

Un mot sur la théorique irréversibilité d’une fonction de hashage ? Le lecteur pourrait se poser la question pour compresser ses données.

Vayel

Je ne trouvais pas ça forcément pertinent, mais je peux ajouter un commentaire dessus.

Le terme "collision" n’est-il pas plus répandu ?

Vayel

Si, je corrigerai.

Tu pourrais indiquer quelle est la fonction de hashage par défaut.

Vayel

Elle me semble tout de même assez complexe. Elle fait intervenir de l’aléatoire et réserve un sort particulier pour les nombres.

Je ne comprends pas le "mais". Plutôt "et devrait" ?

[…]

Edit : en fait tu en parles plus bas. Peut-être serait-il judicieux d’inverser les parties "À quoi servent-ils ?" et "Implémentation", pour que le lecteur ait sa réponse plus rapidement.

Vayel

Peut-être, je voulais attendre d’avoir expliqué l’utilité avant d’en venir à l’implémentation. Je regarderai quelle tête ça peut avoir en inversant les sections. Ou alors juste remonter la remarque sur les mutables ?

Comment Python distingue-t-il les deux situations suivantes ? Arrive-t-il à détecter la surchage de __eq__ ? Comment sait-il si mon implémentation de cette méthode dépend de l’état de l’objet ?

Vayel

L’implémentation d’__eq__ dans une classe fait automatiquement disparaître le __hash__ par défaut (enfin, c’est plutôt que le __hash__ par défaut n’est pas ajouté si __eq__ est redéfinie).

Dans cette partie, tu ne parles que des dictionnaires, mais j’imagine qu’il y a d’autres applications. A défaut de toutes les mentionner, il me semble judicieux d’en présenter au moins une autre classique ou, au minimum, d’en citer l’existence (i.e. d’indiquer que les hashes ne servent pas qu’aux dictionnaires).

Vayel

C’est assez difficile de trouver une autre utilisation de hash en Python que pour les ensembles et les dictionnaires (qui reflètent les deux la même utilisation).

Mais ça peut bien sûr optimiser le calcul de l’égalité entre deux objets, si le hash est stocké en cache (puisque invariable) : si les hashs sont différents, on sait déjà que les objets sont différents.

Ce sujet est verrouillé.