Leverage the power of Python by writing new classes. For interactive reading and executing code blocks and find b09-classes.ipynb, or install Python and JupyterLab locally.
Watch this section as a video
Watch this section as a video on the @Hydro-Morphodynamics channel on YouTube.
La classe des classes¶
Python est un langage intrinsèquement orienté objet et rend le déploiement de classes et d’objets extrêmement facile. Ce chapitre introduit le concept de classes Python et commence par des définitions essentielles.
Qu’est-ce que la programmation orientée objet (OPO)?¶
La programmation orientée objet est un paradigme de programmation qui aligne l’architecture du logiciel sur la réalité. L’orientation de l’objet commence par la conception du logiciel, où un modèle structuré est établi. Le modèle structuré contient des informations sur les objets et leurs abstractions. Le développement et la mise en œuvre de logiciels orientés objet nécessitent une façon structurée de penser et une compréhension conceptuelle des classes, de l’héritage, du polymorphisme et de l’encapsulation.
Objets et catégories¶
Dans le langage informatique, un objet est une instance qui contient des données sous forme de champs (appelés attributs ou propriétés) et de code sous forme de fonctionnalités (fonctions ou méthodes). Les caractéristiques d’un objet permettent l’accès (lecture) et la manipulation de ses champs de données. Les objets ont un concept de self concernant leurs attributs, méthodes et champs de données. self références internes attributs, propriétés, ou méthodes appartenant à un objet.
Dans Python, un objet est une instance d’une classe. Ainsi, un class représente un plan pour de nombreux objets similaires ayant les mêmes attributs et méthodes. Une classe n’utilise pas la mémoire du système et seule son instance (c.-à-d. les objets) utilisera la mémoire.

La forme la plus simple d’une classe en Python comprend seulement quelques énoncés fondamentaux, et il est fortement recommandé d’ajouter une instruction __init__ dans laquelle les variables de classe sont définies. Nous reviendrons sur la déclaration __init__ plus tard dans la section sur les méthodes magic. L’exemple suivant montre l’une des structures de classe les plus simples possibles avec une méthode __init__. Notez l’utilisation de self dans la classe, qui devient object_name.attribute pour les cas de la classe IceCream.
class IceCream:
def __init__(self, *args, **kwargs):
self.flavors=["vanilla", "chocolate", "bread"]
def add_flavor(self, flavor):
self.flavors.append(flavor)
def print_flavors(self):
print(", ".join(self.flavors))
# create an instance of IceCream and use the print_flavors method
some_scoops = IceCream()
some_scoops.add_flavor("lemon")
# the following statements have similar effects
some_scoops.print_flavors()
print(some_scoops.flavors)vanilla, chocolate, bread, lemon
['vanilla', 'chocolate', 'bread', 'lemon']
Héritage¶
Le Cambridge Dictionary définit l’héritage (biologie) comme “Caractéristiques particulières reçues des parents par les gènes”. De même, l’héritage dans l’OOP décrit la relation hiérarchique entre les classes avec est-un-type-de relations. Par exemple, une classe appelée Salmon peut hériter d’une classe appelée Fish. Dans ce cas, Fish est la classe parent (ou super-classe) et Salmon est la classe enfant (ou sous-classe), où Fish pourrait définir des attributs comme preferred_flow_depth ou preferred_flow_velocity avec des méthodes de fuzzification décrivant d’autres préférences d’habitat. Un tel héritage de classe pourrait ressembler à ceci:
# define the parent class Fish
class Fish:
def __init__(self, *args, **kwargs):
self.preferred_flow_depth = float()
self.preferred_flow_velocity = float()
self.species = ""
self.xy_position = tuple()
def print_habitat(self):
print("The species {0} prefers {1}m deep and {2}m/s fast flowing waters.".format(self.species, str(self.preferred_flow_depth), str(self.preferred_flow_velocity)))
def swim_to_position(self, new_position=()):
self.xy_position = new_position
# define the child class Salmon, which inherits (is-a-type-of) from Fish
class Salmon(Fish):
def __init__(self, species, *args, **kwargs):
Fish.__init__(self)
self.family = "salmonidae"
self.species = species
def habitat_function(self, depth, velocity):
self.preferred_flow_depth = depth
self.preferred_flow_velocity = velocity
atlantic_salmon = Salmon("Salmo salar")
atlantic_salmon.habitat_function(depth=0.4, velocity=0.5)
atlantic_salmon.print_habitat()
pacific_salmon = Salmon("Oncorhynchus tshawytscha")
pacific_salmon.habitat_function(depth=0.6, velocity=0.8)
pacific_salmon.print_habitat()The species Salmo salar prefers 0.4m deep and 0.5m/s fast flowing waters.
The species Oncorhynchus tshawytscha prefers 0.6m deep and 0.8m/s fast flowing waters.
Polymorphisme¶
En informatique, le polymorphisme désigne la capacité de présenter la même interface de programmation pour différentes structures de base. Certes, une définition ne peut être beaucoup plus abstraite. Il suffit de se concentrer ici uniquement sur le sens du polymorphisme pertinent pour Python et c’est-à-dire lorsque les classes d’enfants ont des méthodes du même nom que la classe parent. Par exemple, le polymorphisme dans Python est quand nous redéfinissons la fonction swim_to_position de la classe de parent ci-dessus montrée Fish dans la classe d’enfant Salmon.
Encapsulation (Attributs publics et non publics)¶
Le concept d’encapsulation combine les données et les fonctions pour manipuler les données, en vertu desquelles les données et les fonctions sont protégées contre les interférences et les manipulations externes. L’encapsulation est également la référence de data cached en informatique, qui sépare les décisions de conception dans les logiciels concernant les objets susceptibles de changer.
L’un des aspects les plus importants de l’encapsulation est la différenciation entre les variables private et public class. Un attribut private ne peut pas être modifié de l’extérieur (c’est-à-dire qu’il est protégé et ne peut pas être modifié pour une instance d’une classe). En Python, il n’y a pas de variables intrinsèquement private et c’est pourquoi la documentation Python parle des attributs non-public (i.e., _single_leading_underscore defs dans une classe) plutôt que des attributs private. Alors que l’utilisation d’un seul nom de variable de soulignement est plutôt une bonne pratique sans support technique, nous pouvons utiliser des attributs __double_leading_underscore pour imiter un comportement privé avec un mécanisme appelé name mengling. En savoir plus sur les styles de définition variable dans les attributs Python style guide. public peuvent être modifiés de façon externe (c.-à-d., différentes valeurs peuvent être attribuées à public attributs de différentes instances d’une classe).
Dans l’exemple ci-dessus de la classe Salmon, nous utilisons une variable publique appelée self.family. Cependant, l’attribut famille de la classe Salmon est un attribut qui ne devrait pas être modifiable. Un comportement similaire serait souhaitable pour un attribut appelé self.aggregate_state = "frozen" de la classe IceCream. Pour se familiariser avec le concept, le bloc de code suivant définit un autre enfant de la classe Fish avec un attribut non public __family. L’attribut __family n’est pas directement accessible pour les cas de la nouvelle classe enfant appelée Carp. Néanmoins, nous voulons que la classe Carp ait un attribut family et nous voulons pouvoir imprimer sa valeur. C’est pourquoi nous avons besoin d’une méthode spéciale def family(self), qui a le même nom que l’attribut protégé et un décorateur @property. L’exemple ci-dessous comporte une méthode spéciale supplémentaire appelée def family(self, value) qui est embrassée avec un décorateur @property.setter et qui permet de redéfinir la propriété non publique __family (même si c’est logiquement absurde parce que nous ne voulons pas permettre de renommer la propriété __family).
class Carp(Fish):
def __init__(self, species, *args, **kwargs):
Fish.__init__(self)
self.__family = "cyprinidae"
self.species = species
@property
def family(self):
return self.__family
@family.setter
def family(self, value):
self.__family = value
print("family set to \'%s\'" % self.__family)
european_carp = Carp("Cyprinus carpio carpio")
print(european_carp.family)
try:
print(european_carp.__family)
except AttributeError:
print("__family is not directly accessible.")
# re-definition of __family through @family.setter method
european_carp.family="lamnidae"cyprinidae
__family is not directly accessible.
family set to 'lamnidae'
Décors¶
Dans le dernier exemple, nous avons vu l’implémentation du décorateur @property, qui adapte une méthode en un attribut non-appelable (propriété), et du décorateur @attribute.setter pour redéfinir une variable non publique.
Jusqu’ici, nous ne connaissons que les décorateurs comme un moyen efficace de simplifier les fonctions. Cependant, les décorateurs sont un outil encore plus puissant dans la programmation de classes orientées objet, dans lequel les décorateurs peuvent être utilisés pour envelopper des méthodes de classe similaires aux fonctions. Définons un autre enfant de la classe Fish pour explorer le décorateur @property avec ses méthodes deleter, getter et setter.
class Bullhead(Fish):
def __init__(self, species, *args, **kwargs):
Fish.__init__(self)
self.__family = "cottidae"
self.species = species
self.__length = 7.0
@property
def length(self):
return self.__length
@length.setter
def length(self, value):
try:
self.__length = float(value)
except ValueError:
print("Error: Value is not a real number.")
@length.deleter
def length(self):
del self.__length
european_bullhead = Bullhead("Cottus gobio")
# make use of @property.getter, which directly results from the @property-embraced def length method
print(european_bullhead.length)
# make use of @property.setter method
european_bullhead.length = 6.5
print(european_bullhead.length)
# make use of @property.delete method
del european_bullhead.length
try:
print(european_bullhead.length)
except AttributeError:
print("Error: You cannot print a nonexistent property.")7.0
6.5
Error: You cannot print a nonexistent property.
Surcharge et méthodes magiques¶
Les exemples ci-dessus ont introduit une méthode spéciale ou magique appelée __init__. Nous avons déjà vu que __init__ n’est rien de magique en soi et il y a beaucoup d’autres méthodes prédéfinies dans Python. Avant d’arriver aux méthodes magiques, il est important de comprendre le concept de surcharge en Python. Vous êtes-vous déjà demandé pourquoi le même opérateur peut avoir des effets différents selon le type de données? Par exemple, l’opérateur + concatenates strings, mais résume les types de données numériques :
a_string = "vanilla"
b_string = "cream"
print("+ operator applied to strings: " + str(a_string + b_string))
a_number = 50
b_number = 30
print("+ operator applied to integers: " + str(a_number + b_number))+ operator applied to strings: vanillacream
+ operator applied to integers: 80
Ce comportement est appelé operator (ou fonction) surcharge en Python et surcharge est possible en raison de noms prédéfinis de méthodes magiques en Python. Maintenant, nous sommes prêts à plonger dans la piscine de méthodes magiques.
Les méthodes magiques sont l’un des éléments clés qui rendent Python facile et clair à utiliser. En raison de leur déclaration à l’aide de doubles soulignés (__this_is_magic__), les méthodes magiques sont également appelées dunder (double underscore). Les méthodes magiques sont des méthodes spéciales avec des noms fixes et leur magic nom est parce qu’ils n’ont pas besoin d’être directement invoqués. Dans les coulisses, Python utilise constamment des méthodes magiques, par exemple lorsqu’une nouvelle instance d’une classe est attribuée : Lorsque vous écrivez var = MyClass(), Python appelle automatiquement MyClass’es __init__() et __new__() méthodes magiques. Les méthodes magiques s’appliquent également à tout opérateur ou à toute mission (augmentée). Par exemple, l’opérateur binaire + fait chercher la méthode magique __add__. Ainsi, lorsque nous taperons a + b, et que les deux variables sont des exemples de MyClass, Python cherchera la méthode __add__ MyClass pour appliquer a.__add__(b). Si Python ne peut pas trouver la méthode __add__ à MyClass, elle soulève un TypeError: unsupported operand.
Les sections suivantes énumèrent certaines méthodes de magie documentées pour une utilisation dans les classes et les paquets. Ce ne sont que quelques-unes des méthodes magiques les plus courantes et des objets ou attributs magiques plus documentés existent.
Opérateur (binaire) et méthodes d’assignation¶
Pour toute nouvelle classe que nous voulons pouvoir traiter avec un opérateur (par exemple, pour permettre de résumer des objets avec result = object1 + object2), nous devons implémenter (surcharger) les méthodes suivantes.
| Operator | Method | Assignment | Method | |
|---|---|---|---|---|
+ | object.__add__(self, *args, **kwargs) | += | object.__iadd__(self, *args, **kwargs) | |
- | object.__sub__(self, *args, **kwargs) | -= | object.__isub__(self, *args, **kwargs) | |
* | object.__mul__(self, *args, **kwargs) | *= | object.__imul__(self, *args, **kwargs) | |
// | object.__floordiv__(self, *args, **kwargs) | /= | object.__itruediv__(self, *args, **kwargs) | |
/ | object.__truediv__(self, *args, **kwargs) | //= | object.__ifloordiv__(self, *args, **kwargs) | |
% | object.__mod__(self, *args, **kwargs) | %= | object.__imod__(self, *args, **kwargs) | |
** | object.__pow__(self, *args, **kwargs) | **= | object.__ipow__(self, *args, **kwargs) | |
<< | object.__lshift__(self, *args, **kwargs) | <<= | object.__ilshift__(self, *args, **kwargs) | |
>> | object.__rshift__(self, *args, **kwargs) | >>= | object.__irshift__(self, *args, **kwargs) | |
& | object.__and__(self, *args, **kwargs) | &= | object.__iand__(self, *args, **kwargs) | |
^ | object.__xor__(self, *args, **kwargs) | ^= | object.__ixor__(self, *args, **kwargs) | |
| | object.__or__(self, *args, **kwargs) | |= | object.__ior__(self, *args, **kwargs) |
Méthodes de l’opérateur (inactif) et du comparateur¶
De plus, les opérateurs ponctuels ou comparés peuvent être définis ou surchargés. Les opérateurs uniques ne traitent qu’une seule entrée, contrairement aux opérateurs binaires susmentionnés. Un opérateur unique agit sur un seul opérande, comme la négation -x, unary plus +x, ou l’inversion bitwise ~x (à noter que Python n’a pas ++ ou -- incrément/décret opers). De plus, les opérateurs comparatifs (comparateurs) utilisent des méthodes magiques, comme __ne__, comme synonyme pour not equal.
| Operator | Method | Comparator | Method | |
|---|---|---|---|---|
- | object.__neg__(self) | < | object.__lt__(self, *args, **kwargs) | |
+ | object.__pos__(self) | <= | object.__le__(self, *args, **kwargs) | |
abs() | object.__abs__(self) | == | object.__eq__(self, *args, **kwargs) | |
~ | object.__invert__(self) | != | object.__ne__(self, *args, **kwargs) | |
complex() | object.__complex__(self) | >= | object.__ge__(self, *args, **kwargs) | |
int() | object.__int__(self) | > | object.__gt__(self, *args, **kwargs) | |
float() | object.__float__(self) |
Un résumé complet et inclusif des méthodes magiques est fourni dans le Python docs.
Néanmoins, vous vous demandez peut-être comment, dans la pratique, une classe ressemble à qui est capable d’utiliser, par exemple, l’opérateur + avec une méthode __add__? À cette fin, définissons un autre enfant de la classe Fish pour construire un essaim :
class Mackerel(Fish):
def __init__(self, species, *args, **kwargs):
Fish.__init__(self)
self.__family = "scombridae"
self.species = species
self.count = 1
def __add__(self, value):
self.count += value
return self.count
def __mul__(self, multiplier):
self.count *= multiplier
return self.count
atlantic_mackerel = Mackerel("Scomber scombrus")
print(atlantic_mackerel + 1)
print(atlantic_mackerel * 10)2
20
Modèle de classe Python personnalisé¶
Cette section comporte un modèle pour une classe Python3 personnalisée. Le modèle peut être étendu avec des propriétés publiques et non publiques, et des personnalisations de méthodes magiques pour permettre l’utilisation d’opérateurs tels que + ou <=. En fin de compte, il existe de nombreuses options pour écrire une classe personnalisée, mais toutes les classes personnalisées devraient au moins intégrer les méthodes suivantes:
__init__(self, [...)est l’initialisateur de classe (magique), qui est appelé quand une instance de la classe est créée. Plus précisément, il est appelé avec la méthode__new__(cls, [...), qui, en revanche, est rarement utilisée (lire davantage à python.org). L’initialisateur obtient les arguments passés avec lesquels l’objet a été appelé. Par exemple, lorsquevar = MyClass(1, 'vanilla' ), la méthode__init__(self, [...)reçoit1et'vanilla'.__call__(self, [...)permet d’appeler directement une instance de classe. Par exemple,var('cherry')(correspondants àvar.__call__('cherry')) peut être utilisé pour changer de'vanilla'à'cherry'.
Ainsi, un squelette modèle de classe robuste ressemble à ceci:
class NewClass:
def __init__(self, *args, **kwargs):
# initialize any class variable here (all self.attributes should be here)
pass
def methods1_n(self, *args, **kwargs):
# place class methods between the __init__ and the __call__ methods
pass
def __call__(self, *args, **kwargs):
# example prints class structure information to console
print("Class Info: <type> = NewClass (%s)" % os.path.dirname(__file__))
print(dir(self))La compréhension du pouvoir et de la structure des classes et de l’orientation des objets prend du temps et nécessite une pratique. À cette fin, les chapitres sur Graphical User Interfaces et Geospatial Python fournissent d’autres exemples de cours pour se familiariser avec les concepts.
Vérification de la réussite en apprentissage¶
Prenez le test de réussite d’apprentissage pour ce carnet Jupyter.
Unfold QR Code
