Up.oN
#17/04/20225 - Update: #26/04/2025

Inspiré par: pythontutorial.net - python-oop
Résumé par: Le chat - Mistral AI
J'ai ajouté des notes concernant php.

1. Introduction à la POO

1.1 Introduction

Dans cette section, nous explorons les concepts fondamentaux de la programmation orientée objet (POO) en Python

  • Objets et Classes: En Python, tout est un objet. Un objet possède un état (attributs) et des comportements (méthodes). Pour créer un objet, il faut d'abord définir une classe, qui sert de modèle pour créer des objets.
  • Instances: Les objets créés à partir d'une classe sont appelés instances de cette classe.

1.2 Définir une Classe

Syntaxe

Utilisez le mot-clé class suivi du nom de la classe.

class Person:
    pass

Création d'une Instance

Utilisez le nom de la classe suivi de parenthèses pour créer un objet.

person = Person()

1.3 Définir des Attributs d'Instance

Ajout Dynamique

Vous pouvez ajouter des attributs à une instance dynamiquement.

person.name = 'John'

Méthode __init__ (constructeur)

Pour initialiser des attributs pour toutes les instances, utilisez la méthode __init__.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

le mot-clé self n'a pas la même signification qu'en php. L'équivalent en php est $this

1.4 Définir des Méthodes d'Instance

Syntaxe

Définissez des méthodes à l'intérieur de la classe.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
            
    def greet(self):
        return f"Hi, it's {self.name}."        

Appel de Méthode

Utilisez la notation par points pour appeler une méthode.

person = Person('John', 25)
print(person.greet())  # Output: Hi, it's John.            

1.5 Définir des Attributs de Classe

Partage entre Instances

Les attributs de classe sont partagés par toutes les instances.

Cela signifie que si tu modifies un attribut de classe, cette modification sera reflétée dans toutes les instances de cette classe. Les attributs de classe sont souvent utilisés pour définir des constantes ou des variables qui doivent être partagées entre toutes les instances, comme un compteur du nombre d'instances créées.

class Person:
    counter = 0
            
    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.counter += 1

Si je compare avec php, cela équivaut à utilisé static.

En Python, la distinction entre la classe et ses instances est claire et permet une grande flexibilité. C'est moins prononcé en php.

Accès

Accédez aux attributs de classe via la classe ou une instance.

print(Person.counter)  # Output: 2 (après création de deux instances)

1.6 Définir des Méthodes de Classe

Décorateur @classmethod

Utilisez ce décorateur pour définir une méthode de classe.

class Person:
    counter = 0
            
    def __init__(self, name, age):
        self.name = name
        self.age = age
        Person.counter += 1
            
    @classmethod
    def create_anonymous(cls):
        return cls('Anonymous', 22)

pour le mot-clé cls l'équivalent en php est self.

Appel

Appelez une méthode de classe via la classe elle-même.

anonymous = Person.create_anonymous()
print(anonymous.name)  # Output: Anonymous

self en Python vs. PHP

En PHP, bien que tu puisses définir des propriétés et méthodes statiques (qui sont similaires aux attributs et méthodes de classe en Python), la distinction entre les opérations au niveau de la classe et celles au niveau de l'instance n'est pas aussi prononcée.

En PHP, l'accent est souvent mis sur les instances, et les méthodes statiques sont utilisées principalement pour des opérations utilitaires ou de configuration globale.

En python

  • self: est utilisé dans les méthodes d'instance pour faire référence à l'instance actuelle de la classe. Il est le premier paramètre de toute méthode d'instance et permet d'accéder aux attributs et autres méthodes de cette instance.
  • cls: est utilisé dans les méthodes de classe (définies avec @classmethod) pour faire référence à la classe elle-même. Il permet d'accéder aux attributs et méthodes de la classe.

Exemple

class Person:
    counter = 0

    def __init__(self, name):
        self.name = name  # 'self' fait référence à l'instance actuelle
        Person.counter += 1
            
    def greet(self):
        return f"Hi, it's {self.name}."

    @classmethod
    def get_counter(cls):
        return cls.counter  # 'cls' fait référence à la classe

person = Person("Alice")
print(person.greet())  # Output: Hi, it's Alice.
print(Person.get_counter())  # Output: 0

En php

  • $this: est utilisé de manière similaire à self en Python. Il fait référence à l'instance actuelle de la classe et est utilisé pour accéder aux propriétés et méthodes de cette instance.
  • self: est utilisé pour faire référence à la classe elle-même, similaire à cls en Python. Il est utilisé pour accéder aux propriétés et méthodes statiques de la classe.

Exemple

class Person {
    private static $counter = 0;
    public $name;
            
    public function __construct($name) {
        $this->name = $name;  # '$this' fait référence à l'instance actuelle
        self::$counter += 1;  # 'self' fait référence à la classe
    }
            
    public function greet() {
        return "Hi, it's " . $this->name;
    }

    public static function getCounter() {
      return self::$counter;
    }
}
            
$person = new Person("Alice");
echo $person->greet();  # Output: Hi, it's Alice.
echo Person::getCounter();  # Output: 0

Pour résumer

self en Python: Utilisé pour les instances (équivalent à $this en PHP).

cls en Python: Utilisé pour les classes (équivalent à self en PHP).

1.7 Définir des Méthodes Statiques

Décorateur @staticmethod

Utilisez ce décorateur pour définir une méthode statique

class TemperatureConverter:
    @staticmethod
    def celsius_to_fahrenheit(c):
        return 9 * c / 5 + 32
            
    @staticmethod
    def fahrenheit_to_celsius(f):
        return 5 * (f - 32) / 9        

Appel

Appelez une méthode statique via la classe.

f = TemperatureConverter.celsius_to_fahrenheit(30)
print(f)  # Output: 86            

Comparaison avec PHP

Singleton et Patterns de Conception: En PHP, les méthodes statiques sont souvent utilisées pour implémenter des patterns de conception comme le Singleton. En Python, bien que cela soit possible, les méthodes statiques sont plus souvent utilisées pour des fonctions utilitaires ou pour organiser le code.

Accès aux Attributs: En Python, les méthodes statiques n'ont pas accès aux attributs ou méthodes de la classe ou de ses instances, ce qui les rend idéales pour des fonctions purement fonctionnelles.

1.8 Héritage Simple

Syntaxe

Une classe peut hériter d'une autre classe.

class Employee(Person):
    def __init__(self, name, age, job_title):
        super().__init__(name, age)
        self.job_title = job_title

Surcharge de Méthode

Redéfinissez une méthode dans la classe enfant pour la surcharger.

class Employee(Person):
    def greet(self):
        return super().greet() + f" I'm a {self.job_title}."

Appel

Créez une instance de la classe enfant et appelez la méthode surchargée.

employee = Employee('John', 25, 'Python Developer')
print(employee.greet())  # Output: Hi, it's John. I'm a Python Developer.

1.9 Définition d'un Décorateur

En Python, un décorateur est un motif de conception qui permet de modifier ou d'étendre le comportement d'une fonction ou d'une méthode sans changer son code source. Les décorateurs sont souvent utilisés pour ajouter des fonctionnalités telles que la journalisation, la gestion des erreurs, l'authentification, et bien plus encore.

Comment Fonctionne un Décorateur ?

Syntaxe de Base

Un décorateur est une fonction qui prend une autre fonction en argument et étend ou modifie son comportement.

La syntaxe pour appliquer un décorateur utilise le symbole @ suivi du nom du décorateur, placé au-dessus de la fonction à décorer.

Exemple Simple

Voici un exemple simple d'un décorateur qui ajoute une fonctionnalité de journalisation à une fonction :

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper
            
@my_decorator
def say_hello():
    print("Hello!")
            
# Appel de la fonction décorée
say_hello()            

Sortie:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.            

Décorateurs avec Arguments

Les décorateurs peuvent également accepter des arguments pour rendre leur comportement plus flexible.

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(num_times=3)
def greet(name):
    print(f"Hello {name}!")

# Appel de la fonction décorée
greet("Alice")

Sortie:

Hello Alice!
Hello Alice!
Hello Alice!                      

Utilisation des Décorateurs Intégrés

Python propose également des décorateurs intégrés comme:

  • @staticmethod
  • @classmethod
  • @property
Ils sont utilisés pour modifier le comportement des méthodes dans les classes.

Pourquoi Utiliser des Décorateurs

  • Réutilisabilité: Les décorateurs permettent de réutiliser du code en appliquant le même comportement à plusieurs fonctions.
  • Séparation des Préoccupations: Ils aident à séparer les préoccupations en ajoutant des fonctionnalités transversales (comme la journalisation) sans modifier le code principal.
  • Lisibilité: Ils rendent le code plus lisible et maintenable en séparant les responsabilités.

Les décorateurs sont un outil puissant en Python qui permettent d'ajouter des fonctionnalités supplémentaires à des fonctions ou des méthodes de manière élégante et réutilisable. Ils sont largement utilisés dans le développement Python pour améliorer la modularité et la maintenabilité du code.

2. Les classes

Voyons les concepts fondamentaux des classes et des objets en Python, ainsi que la manière de définir une nouvelle classe.

Les Objets

Définition

Un objet est un conteneur qui encapsule des données et des fonctionnalités.

État

Les données d'un objet représentent son état à un moment donné. En Python, cet état est modélisé par des attributs.

Comportement

Les fonctionnalités d'un objet représentent ses comportements, modélisés par des fonctions. Lorsqu'une fonction est associée à un objet, elle devient une méthode de cet objet.

Définir une classe

Syntaxe

Pour définir une classe en Python, utilisez le mot-clé class suivi du nom de la classe et d'un deux-points. Par convention, les noms de classes sont en majuscules, et si le nom contient plusieurs mots, utilisez le format CamelCase.

Exemple

class Person:
    pass        

Le mot-clé pass est utilisé pour indiquer que la classe sera complétée plus tard.

Création d'une instance

Pour créer une instance d'une classe, utilisez le nom de la classe suivi de parenthèses.

person = Person()

Identité de l'objet

L'identité d'un objet peut être obtenue avec la fonction id(), qui retourne un identifiant unique (l'adresse mémoire en CPython).

print(id(person))

Vérification du type

La fonction isinstance() permet de vérifier si un objet est une instance d'une classe donnée.

Une classe est aussi un objet

print(isinstance(person, Person))  # True

Nature des classes

En Python, tout est objet, y compris les classes elles-mêmes. Une classe est une instance de la métaclasse type.

Attributs et comportements

Une classe possède des attributs (comme __name__) et des comportements (comme la création d'instances).

Exemple avec gestion d'erreurs

Voici un exemple de classe simple avec gestion d'erreurs :

class Cyclist:
    def __init__(self, name, age, average_speed):
        if not isinstance(name, str):
            raise ValueError("Le nom doit être une chaîne de caractères.")
        if not isinstance(age, int) or age < 0:
            raise ValueError("L'âge doit être un entier positif.")
        if not isinstance(average_speed, (int, float)) or average_speed < 0:
            raise ValueError("La vitesse moyenne doit être un nombre positif.")
            
        self.name = name
        self.age = age
        self.average_speed = average_speed
            
    def time_for_distance(self, distance):
        if not isinstance(distance, (int, float)) or distance < 0:
            raise ValueError("La distance doit être un nombre positif.")
            
        return distance / self.average_speed
            
    def description(self):
        return f"{self.name} est un cycliste de {self.age} ans avec une vitesse moyenne de {self.average_speed} km/h."
            
try:
    cyclist = Cyclist("Alice", 28, 30.5)
    print(cyclist.description())
            
    distance = 100  # en kilomètres
    time = cyclist.time_for_distance(distance)
    print(f"Temps pour parcourir {distance} km : {time:.2f} heures")
            
    # Exemple avec une erreur
    cyclist_with_error = Cyclist("Bob", -5, 25)
except ValueError as e:
    print(f"Erreur : {e}")            

3. Variables de Classe

3.1 Introduction<

En Python, tout est objet, y compris une classe. Lorsque vous définissez une classe avec le mot-clé class, Python crée un objet avec le même nom que la classe. Par exemple:

class HtmlDocument:
    pass        

Cet exemple définit la classe HtmlDocument et l'objet HtmlDocument. La classe a une propriété __name__ qui renvoie le nom de la classe:

print(HtmlDocument.__name__)  # Output: 'HtmlDocument'

La classe HtmlDocument est une instance de la classe type:

print(isinstance(HtmlDocument, type))  # Output: True

3.2 Définition et Utilisation

Les variables de classe sont liées à la classe et partagées par toutes les instances de cette classe. Par exemple:

class HtmlDocument:
    extension = 'html'
    version = '5'        

Pour accéder aux valeurs des variables de classe, utilisez la notation par point:

print(HtmlDocument.extension)  # Output: 'html'
print(HtmlDocument.version)    # Output: '5'           

Si vous essayez d'accéder à une variable de classe qui n'existe pas, une exception AttributeError sera levée. Vous pouvez utiliser un bloc try-except pour gérer cette erreur:

try:
    print(HtmlDocument.media_type)
except AttributeError as e:
    print(f"Error: {e}")  # Output: Error: type object 'HtmlDocument' has no attribute 'media_type'    

Une autre façon d'obtenir la valeur d'une variable de classe est d'utiliser la fonction getattr():

extension = getattr(HtmlDocument, 'extension')
print(extension)  # Output: 'html'            

Pour éviter une exception si la variable de classe n'existe pas, vous pouvez spécifier une valeur par défaut:

media_type = getattr(HtmlDocument, 'media_type', 'text/html')
print(media_type)  # Output: 'text/html'            

3.3 Modification des Variables de Classe

Pour définir ou modifier une variable de classe, utilisez la notation par point ou la fonction setattr():

HtmlDocument.version = 10
# ou
setattr(HtmlDocument, 'version', 10)            

Vous pouvez également ajouter une nouvelle variable de classe à une classe existante:

HtmlDocument.media_type = 'text/html'
print(HtmlDocument.media_type)  # Output: 'text/html'            

3.4 Suppression des Variables de Classe

Pour supprimer une variable de classe, utilisez la fonction delattr() ou le mot-clé del:

delattr(HtmlDocument, 'version')
# ou
del HtmlDocument.version            

3.5 Stockage des Variables de Classe

Python stocke les variables de classe dans l'attribut __dict__, qui est un dictionnaire. Par exemple:

from pprint import pprint

class HtmlDocument:
    extension = 'html'
    version = '5'
            
HtmlDocument.media_type = 'text/html'
pprint(HtmlDocument.__dict__)            

Cela affichera toutes les variables de classe et autres attributs prédéfinis.

3.6 Attributs de Classe Appelables

Les attributs de classe peuvent être n'importe quel objet, y compris des fonctions. Lorsque vous ajoutez une fonction à une classe, elle devient un attribut de classe appelable:

class HtmlDocument:
    extension = 'html'
    version = '5'
            
    def render():
        print('Rendering the Html doc...')
            
HtmlDocument.render()  # Output: Rendering the Html doc...            

Attributs de Classe Appelables vs. Propriétés de Classe vs. Méthodes de Classe

En Python, les attributs de classe appelables sont des fonctions ou objets appelables définis directement dans le corps de la classe, en dehors de toute méthode. Ils sont appelés directement via la classe elle-même, sans nécessiter une instance de la classe. Par exemple, une fonction définie dans une classe sans le mot-clé self est un attribut de classe appelable. Cela peut sembler similaire aux méthodes statiques en PHP, mais en Python, ces attributs ne sont pas définis avec un décorateur spécifique comme @staticmethod.

Les propriétés de classe, en revanche, sont utilisées pour gérer l'accès aux attributs d'une classe via des méthodes getter et setter. Elles sont définies avec le décorateur @property et permettent de contrôler la lecture, l'écriture et la suppression des attributs d'une manière encapsulée. Cela ressemble aux propriétés en PHP, où vous utilisez des méthodes magiques comme __get et __set.

Les méthodes de classe sont des méthodes qui sont liées à la classe elle-même plutôt qu'à ses instances. Elles sont définies avec le décorateur @classmethod et prennent un premier paramètre implicite cls qui fait référence à la classe. Les méthodes de classe peuvent modifier l'état de la classe, ce qui affectera toutes les instances de la classe. Cela est similaire aux méthodes statiques en PHP, mais avec la capacité d'accéder et de modifier l'état de la classe.

En résumé, les attributs de classe appelables sont des fonctions ou objets appelables définis dans la classe, les propriétés de classe gèrent l'accès aux attributs via des getters et setters, et les méthodes de classe sont des méthodes qui opèrent sur la classe elle-même.

3.7 Résumé

  • Une classe est un objet qui est une instance de la classe type.
  • Les variables de classe sont des attributs de l'objet classe.
  • Utilisez la notation par point ou la fonction getattr() pour obtenir la valeur d'un attribut de classe.
  • Utilisez la notation par point ou la fonction setattr() pour définir la valeur d'un attribut de classe.
  • Python est un langage dynamique, vous pouvez donc assigner une variable de classe à une classe à l'exécution.
  • Python stocke les variables de classe dans l'attribut __dict__, qui est un dictionnaire.

4. Les méthodes

4.1 Définition

Une méthode est une fonction qui est liée à une instance d'une classe. Lorsque vous définissez une fonction à l'intérieur d'une classe, elle est simplement une fonction. Cependant, lorsque vous accédez à cette fonction via un objet (instance de la classe), elle devient une méthode.

4.3 Différence entre fonction et méthode

Une fonction définie dans une classe est un objet de type function. Lorsqu'elle est appelée via une instance de la classe, elle devient un objet de type method.

4.3 Argument implicite self

Lorsqu'une méthode est appelée sur un objet, Python passe implicitement cet objet comme premier argument de la méthode. Par convention, ce premier argument est nommé self. Cela permet à la méthode d'accéder aux attributs et autres méthodes de l'objet.

4.4 Exemples

Exemple de méthode avec self

class Cyclist:
    def ride(self, distance):
        print(f"Le cycliste {self} roule sur une distance de {distance} km.")
            
# Création d'une instance de Cyclist
julian = Cyclist()
            
# Appel de la méthode ride()
julian.ride(50)            

Autre exemple avec gestion d'erreur

class Bicycle:
    def change_gear(self, gear):
        try:
            if gear not in range(1, 23):  # Supposons que le vélo a 22 vitesses
                raise ValueError("La vitesse doit être comprise entre 1 et 22.")
            print(f"Changement de vitesse à {gear}.")
        except TypeError as e:
            print(f"Erreur de type: {e}")
        except ValueError as e:
            print(e)
            
# Création d'une instance de Bicycle
my_bike = Bicycle()
            
# Appel de la méthode change_gear()
my_bike.change_gear(10)   # Affiche "Changement de vitesse à 10."
my_bike.change_gear(25)   # Affiche une erreur de valeur
my_bike.change_gear("10") # Affiche une erreur de type

5. Les méthodes

5.1 Rôle de __init__

La méthode __init__ est utilisée pour initialiser les attributs d'un objet lors de sa création. Elle est appelée automatiquement par Python après la création d'une nouvelle instance de classe.

Elle n'est pas un constructeur au sens strict, car elle n'alloue pas la mémoire pour l'objet. Elle initialise seulement les attributs de l'objet.

5.2 Syntaxe et Utilisation

La méthode __init__ est définie avec deux underscores (__) de chaque côté, ce qui en fait une méthode spéciale (ou "dunder" pour "double underscore").

Elle prend toujours au moins un paramètre, self, qui fait référence à l'instance actuelle de la classe.

5.3 Exemple de Base

class Cyclist:
    def __init__(self, name, age):
        self.name = name
        self.age = age
            
# Création d'une instance de Cyclist
cyclist = Cyclist('Tadej', 25)
print(f"Je suis {cyclist.name}. J'ai {cyclist.age} ans.")            

5.4 Paramètres par Défaut

Les paramètres de __init__ peuvent avoir des valeurs par défaut. Si aucune valeur n'est fournie lors de la création de l'objet, la valeur par défaut est utilisée.

class Cyclist:
    def __init__(self, name, age=22):
        self.name = name
        self.age = age
            
# Utilisation de la valeur par défaut pour l'âge
cyclist = Cyclist('Jonas')
print(f"Je suis {cyclist.name}. J'ai {cyclist.age} ans.")        

5.5 Gestion des Erreurs

Si __init__ a des paramètres autres que self, il est nécessaire de fournir les arguments correspondants lors de la création de l'objet, sinon une erreur sera levée.

class Cyclist:
    def __init__(self, name, age):
        if not isinstance(name, str):
            raise ValueError("Le nom doit être une chaîne de caractères.")
        if not isinstance(age, int):
            raise ValueError("L'âge doit être un entier.")
        self.name = name
        self.age = age
            
try:
    cyclist = Cyclist('Tadej', '25')  # Cela lèvera une erreur
except ValueError as e:
    print(e)            

5.6 Conclusion

La méthode __init__ est essentielle pour initialiser les objets en POO Python. Elle permet de définir l'état initial des objets dès leur création, en utilisant des paramètres par défaut si nécessaire. Il est important de bien comprendre son rôle pour maîtriser la création et l'initialisation des objets en Python.

6. Les Variables d'instance

6.1 Introduction

Variables d'instance vs. Variables de classe: Les variables d'instance sont spécifiques à une instance particulière d'une classe, tandis que les variables de classe sont partagées par toutes les instances de la classe.

Attribut __dict__: Les variables d'instance sont stockées dans l'attribut __dict__ de l'instance. Cet attribut est un dictionnaire mutable, ce qui permet d'ajouter, de modifier ou de supprimer des éléments.

Accès aux variables: Lorsqu'on accède à une variable via une instance, Python cherche d'abord dans le __dict__ de l'instance. Si la variable n'est pas trouvée, Python cherche ensuite dans le __dict__ de la classe.

6.2 Initialisation des variables d'instance

Méthode __init__: Les variables d'instance sont généralement initialisées dans la méthode __init__ de la classe. Cela permet de définir les valeurs initiales des variables d'instance lors de la création d'une nouvelle instance.

6.3 Exemple de code

Voici un exemple illustrant ces concepts avec un thème lié au cyclisme:

class Cycliste:
    sport = "Cyclisme"
    type_course = "Route"
            
    def __init__(self, nom, equipe):
        self.nom = nom
        self.equipe = equipe
            
# Création d'une instance de la classe Cycliste
pogacar = Cycliste("Tadej Pogačar", "UAE Team Emirates")
            
# Accès aux variables d'instance
print(pogacar.nom)  # Output: Tadej Pogačar
print(pogacar.equipe)  # Output: UAE Team Emirates
            
# Accès aux variables de classe via l'instance
print(pogacar.sport)  # Output: Cyclisme
print(pogacar.type_course)  # Output: Route
            
# Modification d'une variable d'instance
pogacar.equipe = "Nouvelle Équipe"
print(pogacar.equipe)  # Output: Nouvelle Équipe
            
# Ajout d'une nouvelle variable d'instance
pogacar.age = 26
print(pogacar.age)  # Output: 26        

6.4 Points clés

  • Les variables d'instance sont spécifiques à chaque instance et sont stockées dans le __dict__ de l'instance.
  • Les variables de classe sont partagées par toutes les instances et sont stockées dans le __dict__ de la classe.
  • Les variables d'instance sont généralement initialisées dans la méthode __init__.

7. Les Attributs Privés

Les attributs privés en programmation orientée objet (POO) sont une notion fondamentale pour encapsuler les données et restreindre l'accès direct aux variables d'une classe.

7.1 Définition

Les attributs privés sont des variables d'instance destinées à être utilisées uniquement à l'intérieur de la classe. Ils ne doivent pas être accessibles directement depuis l'extérieur de la classe.

7.2 Syntaxe

En Python, les attributs privés sont définis en utilisant un préfixe de double underscore (__). Par exemple: __attribut_prive.

7.3 Encapsulation

L'encapsulation est le principe de masquer les détails internes d'une classe et de ne permettre l'accès qu'à travers des méthodes publiques. Cela protège les données internes et permet de contrôler comment elles sont modifiées.

7.4 Accès aux Attributs Privés

Les attributs privés ne peuvent pas être accédés directement depuis l'extérieur de la classe. Pour y accéder ou les modifier, on utilise des méthodes publiques appelées getters et setters.

7.5 Getters et Setters

Les getters sont des méthodes qui retournent la valeur d'un attribut privé.

Les setters sont des méthodes qui permettent de modifier la valeur d'un attribut privé, souvent avec des vérifications ou des validations.

7.6 Exemple de Code

Voici un exemple simple pour illustrer l'utilisation des attributs privés dans une classe Cycliste:

class Cycliste:
    def __init__(self, nom, equipe):
        self.__nom = nom
        self.__equipe = equipe

    # Getter pour l'attribut privé __nom
    def get_nom(self):
        return self.__nom

    # Setter pour l'attribut privé __nom
    def set_nom(self, nom):
        if isinstance(nom, str) and nom:
            self.__nom = nom
        else:
            raise ValueError("Le nom doit être une chaîne non vide.")

    # Getter pour l'attribut privé __equipe
    def get_equipe(self):
        return self.__equipe

    # Setter pour l'attribut privé __equipe
    def set_equipe(self, equipe):
        if isinstance(equipe, str) and equipe:
            self.__equipe = equipe
        else:
            raise ValueError("L'équipe doit être une chaîne non vide.")

# Utilisation de la classe Cycliste
try:
    cycliste = Cycliste("Romain Bardet", "Team DSM")
    print(cycliste.get_nom())  # Affiche: Romain Bardet
    cycliste.set_nom("Lenny Martinez")
    print(cycliste.get_nom())  # Affiche: Lenny Martinez
    cycliste.set_equipe("Groupama-FDJ")
    print(cycliste.get_equipe())  # Affiche: Groupama-FDJ
except ValueError as e:
    print(e)

7.7 Notions Complémentaires

Mangling des Noms

Python utilise un mécanisme appelé "name mangling" pour les attributs privés. Cela signifie que l'interpréteur modifie le nom de l'attribut pour inclure le nom de la classe, ce qui rend plus difficile l'accès accidentel depuis l'extérieur de la classe.

Conventions

Bien que l'utilisation de __ rende un attribut privé, il est également courant d'utiliser un seul underscore (_) pour indiquer qu'un attribut ou une méthode est destiné à un usage interne. Cependant, cela ne l'empêche pas d'être accessible depuis l'extérieur de la classe.

Bonnes Pratiques

Utilisez des attributs privés pour protéger les données sensibles et maintenir l'intégrité des objets.

Fournissez des méthodes publiques pour permettre un accès contrôlé aux attributs privés.

7.8 Encapsulation et Attributs Privés: PHP vs PY

L'encapsulation est un principe fondamental de la programmation orientée objet (POO) qui consiste à restreindre l'accès direct à certaines composantes d'un objet, telles que ses attributs et méthodes. Cela permet de protéger l'intégrité des données et de contrôler comment elles sont manipulées. Cependant, la manière dont ce concept est implémenté varie d'un langage à l'autre.

PHP utilise des mots-clés comme private, protected, et public pour définir explicitement le niveau d'accès des attributs et méthodes.

Python favorise la simplicité et la responsabilité du développeur. L'encapsulation repose davantage sur des conventions et des pratiques de codage que sur des restrictions imposées par le langage lui-même.

8. Les Attributs de classe

8.1 Définition et accès

Les attributs de classe en Python sont des variables partagées par toutes les instances d'une classe. Ils sont définis en dehors de la méthode __init__() et peuvent être accédés via le nom de la classe ou une instance de la classe.

Un attribut de classe

est défini en dehors de la méthode __init__().

On peut y accéder via class_name.class_attribute ou object_name.class_attribute.

8.2 Fonctionnement

Lorsqu'on accède à un attribut via une instance, Python cherche d'abord dans les attributs d'instance, puis dans les attributs de classe.

Lorsqu'on accède à un attribut via la classe, Python cherche directement dans les attributs de classe.

8.3 Utilisations des attributs de classe

Stocker des constantes de classe: Les constantes qui ne changent pas d'une instance à l'autre peuvent être stockées comme attributs de classe.

Suivre des données à travers toutes les instances: Un attribut de classe peut être utilisé pour garder une trace des instances créées.

Définir des valeurs par défaut: Les attributs de classe peuvent servir à définir des valeurs par défaut pour toutes les instances.

8.4 Exemple de Code

class Cyclist:
    # Attribut de classe pour stocker une constante
    average_speed = 35  # km/h
    cyclists_list = []
            
    def __init__(self, name, speed):
        self.name = name
        self.speed = speed
        # Ajouter l'instance à la liste des cyclistes
        Cyclist.cyclists_list.append(self)
            
    def time_to_complete(self, distance):
        try:
            return distance / self.speed
        except ZeroDivisionError:
            return "La vitesse ne peut pas être nulle."
            
    def display_cyclist(self):
        return f"{self.name} roule à {self.speed} km/h."
            
# Création d'instances
pogačar = Cyclist("Tadej Pogačar", 40)
van_der_poel = Cyclist("Mathieu van der Poel", 38)
            
# Accès aux attributs de classe
print(Cyclist.average_speed)  # 35
print(len(Cyclist.cyclists_list))  # 2
            
# Utilisation des méthodes
print(pogačar.display_cyclist())  # Tadej Pogačar roule à 40 km/h.
print(pogačar.time_to_complete(100))  # 2.5        

Dans cet exemple, average_speed et cyclists_list sont des attributs de classe. Ils sont partagés par toutes les instances de Cyclist. La méthode time_to_complete inclut une gestion d'erreur pour éviter une division par zéro

9. Les Attributs de classe

Les méthodes statiques en Python sont des méthodes définies au sein d'une classe qui ne sont liées ni à une instance spécifique (objet) ni à la classe elle-même. Elles ne peuvent donc pas accéder ou modifier l'état de l'objet ou de la classe

9.1 Définition et utilisation

Les méthodes statiques sont définies à l'aide du décorateur @staticmethod.

Elles ne reçoivent pas implicitement de paramètre self ou cls, ce qui signifie qu'elles ne peuvent pas interagir avec les attributs de l'instance ou de la classe.

9.2 Syntaxe

class NomClasse:
    @staticmethod
    def nom_methode_statique(parametres):
        pass

Appel: NomClasse.nom_methode_statique()

9.3 Différences avec les méthodes de classe

Les méthodes de classe reçoivent implicitement le paramètre cls, permettant d'accéder et de modifier l'état de la classe.

Les méthodes statiques ne reçoivent pas ce paramètre et ne peuvent donc pas interagir avec l'état de la classe.

9.4 Utilisation pratique

Les méthodes statiques sont souvent utilisées pour définir des fonctions utilitaires ou pour regrouper des fonctions logiquement liées dans une classe.

9.5 Exemple

Voici un exemple de classe CyclingUtility avec des méthodes statiques pour convertir des unités de mesure courantes en cyclisme.

class CyclingUtility:
    KILOMETERS = 'km'
    MILES = 'mi'

    @staticmethod
    def kilometers_to_miles(km):
        return km * 0.621371

    @staticmethod
    def miles_to_kilometers(mi):
        return mi / 0.621371

    @staticmethod
    def format_distance(value, unit):
        symbol = ''
        if unit == CyclingUtility.KILOMETERS:
            symbol = 'km'
        elif unit == CyclingUtility.MILES:
            symbol = 'mi'
        return f'{value:.2f}{symbol}'

# Utilisation
try:
    distance_mi = CyclingUtility.kilometers_to_miles(100)
    print(CyclingUtility.format_distance(distance_mi, CyclingUtility.MILES))
except Exception as e:
    print(f"Une erreur s'est produite: {e}")