Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau basique

Typage dynamique

En première semaine, nous avons rapidement mentionné les concepts de typage statique et dynamique.

Avec la fonction prédéfinie isinstance - qui peut être par ailleurs utile dans d’autres contextes - vous pouvez facilement :

Voyons tout de suite sur un exemple simple comment on pourrait définir une fonction qui travaille sur un entier, mais qui par commodité peut aussi accepter un entier passé comme une chaîne de caractères, ou même une liste d’entiers (auquel cas on renvoie la liste des factorielles) :

def factoriel(argument):
    # si on reçoit un entier
    if isinstance(argument, int):              # (*)
        return 1 if argument <= 1 else argument * factoriel(argument - 1)
    # convertir en entier si on reçoit une chaîne
    elif isinstance(argument, str):
        return factoriel(int(argument))
    # la liste des résultats si on reçoit un tuple ou une liste 
    elif isinstance(argument, (tuple, list)):  # (**)
        return [factoriel(i) for i in argument]
    # sinon on lève une exception
    else:
        raise TypeError(argument)
print("entier", factoriel(4))
print("chaine", factoriel("8"))
print("tuple", factoriel((4, 8)))
entier 24
chaine 40320
tuple [24, 40320]

Remarquez que la fonction isinstance possède elle-même une logique de ce genre, puisqu’en ligne 3 (*) nous lui avons passé en deuxième argument un type (int), alors qu’en ligne 11 (**) on lui a passé un tuple de deux types. Dans ce second cas naturellement, elle vérifie si l’objet (le premier argument) est de l’un des types mentionnés dans le tuple.

Complément - niveau intermédiaire

Le module types

Le module types définit un certain nombre de constantes qui peuvent être utiles dans ce contexte - vous trouverez une liste exhaustive à la fin de ce notebook. Par exemple :

from types import FunctionType
isinstance(factoriel, FunctionType)
The history saving thread hit an unexpected error (OperationalError('no such table: history')).History will not be written to the database.
True

Mais méfiez-vous toutefois des fonctions built-in, qui sont de type BuiltinFunctionType

from types import BuiltinFunctionType
isinstance(len, BuiltinFunctionType)
True
# alors qu'on pourrait penser que
isinstance(len, FunctionType)
False

isinstance vs type

Il est recommandé d’utiliser isinstance par rapport à la fonction type. Tout d’abord, cela permet, on vient de le voir, de prendre en compte plusieurs types.

Mais aussi et surtout isinstance supporte la notion d’héritage qui est centrale dans le cadre de la programmation orientée objet, sur laquelle nous allons anticiper un tout petit peu par rapport aux présentations de la semaine prochaine.

Avec la programmation objet, vous pouvez définir vos propres types. On peut par exemple définir une classe Animal qui convient pour tous les animaux, puis définir une sous-classe Mammifere. On dit que la classe Mammifere hérite de la classe Animal, et on l’appelle sous-classe parce qu’elle représente une partie des animaux ; et donc tout ce qu’on peut faire sur les animaux peut être fait sur les mammifères.

En voici une implémentation très rudimentaire, uniquement pour illustrer le principe de l’héritage. Si ce qui suit vous semble difficile à comprendre, pas d’inquiétude, nous reviendrons sur ce sujet lorsque nous parlerons des classes.

class Animal:
    def __init__(self, name):
        self.name = name

class Mammifere(Animal):
    def __init__(self, name):
        Animal.__init__(self, name)

Ce qui nous intéresse dans l’immédiat c’est que isinstance permet dans ce contexte de faire des choses qu’on ne peut pas faire directement avec la fonction type, comme ceci :

# pour créer un objet de type `Animal` (méthode __init__)
requin = Animal('requin')
# idem pour un Mammifere
baleine = Mammifere('baleine')

# bien sûr ici la réponse est 'True'
print("l'objet baleine est-il un mammifère ?", isinstance(baleine, Mammifere))
l'objet baleine est-il un mammifère ? True
# ici c'est moins évident, mais la réponse est 'True' aussi
print("l'objet baleine est-il un animal ?", isinstance(baleine, Animal))
l'objet baleine est-il un animal ? True

Vous voyez qu’ici, bien que l’objet baleine soit de type Mammifere, on peut le considérer comme étant aussi de type Animal.

Ceci est motivé de la façon suivante : comme on l’a dit plus haut, tout ce qu’on peut faire (en matière notamment d’envoi de méthodes) sur un objet de type Animal, on peut le faire sur un objet de type Mammifere. Dit en termes ensemblistes, l’ensemble des mammifères est inclus dans l’ensemble des animaux.

Annexe - Les symboles du module types

Vous pouvez consulter la documentation du module types.

# voici par ailleurs la liste de ses attributs
import types 
dir(types)
['AsyncGeneratorType', 'BuiltinFunctionType', 'BuiltinMethodType', 'CellType', 'ClassMethodDescriptorType', 'CodeType', 'CoroutineType', 'DynamicClassAttribute', 'EllipsisType', 'FrameType', 'FunctionType', 'GeneratorType', 'GenericAlias', 'GetSetDescriptorType', 'LambdaType', 'MappingProxyType', 'MemberDescriptorType', 'MethodDescriptorType', 'MethodType', 'MethodWrapperType', 'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace', 'TracebackType', 'UnionType', 'WrapperDescriptorType', '_GeneratorWrapper', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_calculate_meta', 'coroutine', 'get_original_bases', 'new_class', 'prepare_class', 'resolve_bases']