Python >> Tutoriel Python >  >> Python

Étapes simples pour créer votre propre classe en Python

Savez-vous créer votre propre classe en Python ? L'écriture de classes personnalisées et d'objets personnalisés en Python rend votre code plus clair, plus lisible et plus facile à maintenir.

Avant de commencer, si vous avez encore besoin de bonnes raisons d'apprendre Python, Rebecca peut vous aider ici.

Le concept de programmation orientée objet est apparu dans les années 60, mais sa popularité n'a commencé à croître que dans les années 90. Aujourd'hui, la programmation orientée objet est omniprésente et constitue un paradigme de programmation essentiel à comprendre.

La programmation orientée objet consiste à créer des objets personnalisés. Un objet est un groupe de fonctions et de variables interdépendantes qui interagissent ensemble. Si vous n'êtes pas familier avec le concept de fonctions, Kateryna en parle en détail ici.

Contrairement à la programmation orientée procédure, la programmation orientée objet réduit la complexité du code, le rendant plus clair et plus facile à entretenir. Il permet également de cacher des données par encapsulation. La programmation orientée procédure manque de cette sécurité, car toutes les fonctions peuvent accéder aux données. La programmation orientée objet peut être un peu difficile, et je vous recommande de suivre notre parcours de programmation Python.

Dans cet article, je vous explique ce qu'est une classe personnalisée en Python et comment vous pouvez en créer une à l'aide de constructeurs. Ensuite, j'explique comment définir les attributs de classe et les différents types de méthodes. Enfin, après un mot sur la visibilité des classes personnalisées Python, vous apprenez à comparer et à effectuer des opérations sur des objets personnalisés Python.

Création d'une classe personnalisée en Python à l'aide d'un constructeur

Une classe est une collection d'objets. Il s'agit d'une structure de données définie par l'utilisateur, créée avec le mot clé class pour conserver les éléments liés ensemble. Ainsi, une classe est un regroupement de constructions orientées objet.

Écrivons une simple classe vide :

class Pokemon: 
	Pass

# instantiate the class Pokemon and assign it to a variable pokemon
pokemon = Pokemon()
print(pokemon)

La sortie :

<__main__.Pokemon object at 0x0000027B56ADD730>

Comme notre classe personnalisée Python est vide, elle renvoie simplement l'adresse où l'objet est stocké.

Dans la programmation orientée objet, les propriétés d'un objet personnalisé sont définies par des attributs, tandis que ses méthodes définissent son comportement. Il existe trois types de méthodes :

  • Méthodes d'instance
  • Méthodes de classe
  • Méthodes statiques

En Python, le self Le mot-clé représente une instance d'une classe. Il fonctionne comme un handle pour accéder aux membres de la classe, tels que les attributs des méthodes de la classe. C'est le premier argument du __init__() et est appelée automatiquement pour initialiser les attributs de la classe avec les valeurs définies par l'utilisateur.

Exécutons un exemple :

class Pokemon:

    def __init__(self): 
        print("calling __init__() constructor...")

pokemon = Pokemon()

La sortie :

calling __init__() constructor...

Cependant, une classe personnalisée en Python est inutile si elle n'est pas associée à des fonctionnalités. Les fonctionnalités sont ajoutées à l'aide d'attributs et agissent comme des conteneurs pour les données et les fonctions de ces attributs. Ces fonctions sont appelées méthodes.

Attributs d'instance et de classe dans une classe personnalisée Python

Mettons à jour le Pokemon classe avec un init() méthode qui crée name et age les attributs. Ces attributs sont appelés attributs d'instance.

class Pokemon:
    def __init__(self, name, attack):
        self.name = name 
        self.attack = attack

Maintenant, définissons un attribut de classe pour notre Pokemon classe :

class Pokemon:
    # Class attribute
    species = "Mouse"
    def __init__(self, name, attack): 
        self.name = name
        self.attack = attack

Nous utilisons des attributs de classe pour définir des propriétés avec la même valeur pour chaque instance de classe et des attributs d'instance pour les propriétés qui varient d'une instance à l'autre.

Créons des Pokémon.

class Pokemon:
    # Class attribute
    species = "Mouse"
    def __init__(self, name, attack): 
        self.name = name
        self.attack = attack
        
pikachu = Pokemon("Pikachu", "Double Kick")
raichu = Pokemon("Raichu", "Thunder Punch")

Après avoir créé le Pokemon instances, nous pouvons accéder à leurs attributs d'instance en utilisant la notation par points, [instance name].[attribute name] , comme ceux-ci :

>>> pikachu.name
'Pikachu'
>>> pikachu.attack
'Double Kick'
>>> pikachu.species
'Mouse'
>>> raichu.name
'Raichu'
>>> raichu.attack
'Thunder Punch'

L'un des principaux avantages de l'organisation des données avec des classes est que les instances sont garanties d'avoir les attributs attendus. Cependant, cela ne signifie pas que nous ne pouvons pas modifier leur valeur dynamiquement, par exemple :

>>> pikachu.attack = "Thunder Shock"
>>> pikachu.attack
'Thunder Shock'

Méthodes d'instance dans les classes personnalisées Python

Les méthodes d'instance sont des fonctions définies à l'intérieur d'une classe et ne peuvent être appelées qu'à partir d'une instance de cette classe. Comme __init__() , le premier paramètre d'une méthode d'instance est toujours self .

Définissons quelques méthodes d'instance pour notre classe personnalisée Python Pokemon .

class Pokemon:
    # Class attribute
    species = "Mouse"
    def __init__(self, name, attack): 
        self.name = name
        self.attack = attack

    # One instance method
    def description(self):
        return f"{self.name} favorite attack is {self.attack}"

    # A second instance method
    def speak(self, sound):
        return f"{self.name} says {sound}"

Le self mot-clé est essentiel. Sans cela, nous ne pouvons pas accéder aux attributs et aux méthodes d'une classe personnalisée en Python, et cela entraîne une erreur. En d'autres termes, il lie les attributs avec les arguments donnés.

Utilisons nos nouvelles méthodes d'instance en créant un nouveau Pokemon instance dynamiquement :

>>> pichu = Pokemon("Pichu", "Nuzzle")
>>> pichu.description()
"Pichu favorite attack's is Nuzzle"
>>> pichu.speak("pichu pichu")
'Pichu says pichu pichu'

Dans le Pokemon ci-dessus classe, le description() La méthode renvoie une chaîne contenant des informations sur le Pokemon instance pichu . Lorsque nous écrivons une classe personnalisée Python, c'est une bonne idée d'avoir une méthode qui renvoie une chaîne contenant des informations utiles sur l'instance de la classe.

Méthodes de classe dans les classes personnalisées Python

La méthode de classe existe pour définir ou obtenir le statut d'une classe. Ils ne peuvent pas accéder ou modifier des données d'instance spécifiques. Les méthodes sont là pour décrire le comportement des objets et sont définies à l'intérieur d'une classe.

Une méthode de classe doit être définie en utilisant le @classmethod décorateur. Ils prennent également un paramètre par défaut cls , qui pointe vers la classe. Il n'est pas obligatoire de le nommer cls , mais il est bon de suivre les conventions.

Les méthodes de classe sont utilisées pour créer une méthode de fabrique. Les méthodes de fabrique renvoient différents objets de classe en fonction du cas d'utilisation.

Continuons avec Pokemon :

class Pokemon:
    def __init__(self, names):
        self.names = names

    def __repr__(self):
        return f'Pokemon({self.names})'

    @classmethod
    def mouse(cls):
        return cls(['Pichu', 'Pikachu', 'Raichu'])

    @classmethod
    def hummingbird(cls):
        return cls(['Florabri', 'Floressum'])

Au lieu d'appeler le Pokemon constructeur directement, j'utilise le cls argument dans le mouse et hummingbird méthodes de classe. Lorsque je change le nom de la classe, je n'ai pas besoin de mettre à jour le nom du constructeur dans chaque méthode de classe.

__repr__() est utilisé pour représenter un objet d'une classe sous forme de chaîne. Cela signifie que la sortie est une représentation sous forme de chaîne de l'objet. Sans cela, la sortie de Pokemon.mouse() est :

>>> Pokemon.mouse()
<__main__.Pokemon at 0x1d219dcb4f0>

Voici ce que font ces méthodes de classe :

>>> Pokemon.mouse()
Pokemon(['Pichu', 'Pikachu', 'Raichu'])
>>> Pokemon.hummingbird()
Pokemon(['Florabri', 'Floressum'])

Nous avons donc utilisé des méthodes de classe pour créer de nouveaux objets Pokémon déjà configurés comme nous le souhaitons.

Méthodes statiques dans les classes personnalisées Python

Les méthodes statiques ne peuvent pas accéder aux données de classe car elles sont autosuffisantes et peuvent fonctionner seules. Ils ne sont attachés à aucun attribut de classe, ils ne peuvent donc pas obtenir ou définir l'état de l'instance ou de la classe.

Pour les définir, nous devons utiliser le @staticmethod décorateur. Contrairement aux méthodes d'instance et de classe, nous n'avons pas besoin de passer de paramètre par défaut.

Les fonctions statiques sont utilisées pour créer des fonctions utilitaires permettant d'effectuer des tâches de programmation de routine. Écrivons un exemple dans lequel nous avons une méthode statique pour calculer les dégâts causés par une attaque de Pokémon :

class Pokemon: 
    def __init__(self, power, level, names):
        self.power = power
        self.level = level
        self.names = names
        
    def __repr__(self):
        return (f'Pokemon({self.power}, '
                f'{self.level}, '
                f'{self.names})')
    
    def total_damage(self):
        return self.damage(self.power, self.level)

    @staticmethod
    def damage(power, level):
        return (power * level * 2) / 50

J'ai modifié le constructeur pour accepter le power et level arguments et __repr__() pour l'afficher. J'ai aussi ajouté un total_damage() méthode d'instance qui calcule et renvoie les dégâts lorsque le Pokémon attaque. Et, au lieu de calculer le niveau des dommages directement dans total_damage() , j'ai ajouté une formule pour calculer les dommages dans un damage() séparé méthode statique.

Essayons :

>>> charmander = Pokemon(20, 8, "Charmander")
>>> charmander.total_damage()
6.4
>>> charmander.damage(20, 8)
6.4

Le cas d'utilisation ici est très simple. Les méthodes statiques n'ont pas accès à cls ou self . Elles se comportent comme des fonctions normales mais appartiennent à l'espace de noms de la classe et ne sont généralement pas liées à un cycle de vie d'objet. Le damage() ci-dessus est complètement indépendante de la classe, ce qui rend les tests beaucoup plus faciles à gérer.

De plus, nous n'avons pas à nous soucier de configurer une instance de classe complète avant de tester la méthode dans un test unitaire. Nous procédons comme nous le ferions si nous testions une fonction régulière, ce qui facilite la maintenance future.

Pour renforcer ce point, voyons ce qui se passe si nous essayons d'appeler ces méthodes sur la classe elle-même sans créer d'instance de la classe :

class NewClass:
    def method(self):
        return 'Calling instance method...', self

    @classmethod
    def classmethod(cls):
        return 'Calling class method...', cls

    @staticmethod
    def staticmethod():
        return 'Calling static method...'
>>> NewClass.method()
TypeError: method() missing 1 required positional argument: 'self'
>>> NewClass.classmethod()
('Calling class method...', __main__.NewClass)
>>> NewClass.staticmethod()
'Calling static method...'

Nous avons pu appeler classmethod() et staticmethod(), mais la tentative d'appel de la méthode d'instance method() a échoué avec une TypeError. C'est parce que nous avons essayé d'appeler une fonction d'instance directement sur le plan de classe lui-même sans créer d'instance de la classe. Python n'a pas pu remplir l'argument self, ce qui a entraîné l'échec de l'appel. Le fait que nous puissions appeler staticmethod() sans problème, en revanche, prouve que la méthode est complètement indépendante du reste de la classe.

Visibilité dans les classes personnalisées Python

Les langages de programmation orientés objet comme C++ et Java contrôlent l'accès aux classes avec les mots-clés public, private et protected. Python conceptualise les modificateurs d'accès publics, protégés et privés, contrairement à d'autres langages comme C#, Java et C++.

Membres publics d'une classe personnalisée en Python

Les membres publics sont accessibles depuis l'extérieur de la classe. Cela signifie que nous pouvons modifier librement les attributs de la classe sans aucune restriction. Le même objet de classe est requis pour invoquer une méthode publique. Ceci est fait pour suivre le principe de l'encapsulation des données. En Python, les membres de classe sont publics par défaut.

Membres protégés d'une classe personnalisée en Python

Les membres protégés d'une classe sont accessibles depuis la classe et sont également disponibles pour ses sous-classes.

Python ne dispose d'aucun mécanisme pour restreindre l'accès à une variable d'instance ou à une méthode. Au lieu de cela, il a pour convention de préfixer le nom de la variable ou de la méthode avec un trait de soulignement simple ou double pour émuler le comportement des spécificateurs d'accès protégés et privés. Pour protéger une variable d'instance, un préfixe à un seul trait de soulignement ("_ ”) est ajouté, empêchant ainsi son accès sauf dans une sous-classe.

Notez que cela n'empêche pas les variables d'instance d'accéder ou de modifier l'instance.

Membres privés d'une classe personnalisée en Python

Les membres privés de la classe ne peuvent pas accéder à l'environnement depuis l'extérieur de la classe et ne peuvent être gérés qu'à partir de la classe elle-même. Toute tentative de modification de la variable entraîne un AttributeError .

Un membre privé est nommé en ajoutant un préfixe à double trait de soulignement ("__ ”) avant le nom de la variable. Python effectue une manipulation de noms de variables privées, et chaque membre avec un double trait de soulignement est remplacé par _object._class__variable . Ainsi, il est toujours possible d'y accéder depuis l'extérieur de la classe, mais la pratique doit être évitée.

Méthodes de comparaison de deux objets personnalisés Python

En codage, nous utilisons des opérateurs tels que>. Cependant, vous devez utiliser __gt__() et les goûts pour implémenter votre fonctionnalité de classe personnalisée Python.

Les méthodes ci-dessous sont utilisées pour la comparaison des attributs d'un objet. Pour vous aider à vous en souvenir, regardez les majuscules dans les commentaires.

class Value:
    def __init__(self, baz):
        self.baz = baz
    # Less Than operator
    def __lt__(self, obj2):
        return self.baz < obj2.baz
    # Greater Than operator
    def __gt__(self, obj2):
        return self.baz > obj2.baz
    # Less than or Equal operator
    def __le__(self, obj2):
        return self.baz <= obj2.baz
    # Greater than or Equal operator
    def __ge__(self, obj2):
        return self.baz >= obj2.baz
    # EQual operator
    def __eq__(self, obj2):
        return self.baz == obj2.baz
    # unequal (Not Equal) operator
    def __ne__(self, obj2):
        return self.baz != obj2.baz
foo = Value(6)
bar = Value(9)
print(
    foo < bar,
    foo > bar,
    foo <= bar,
    foo >= bar,
    foo == bar,
    foo != bar
)

La sortie :

True False True False False True

Les instances foo et bar contiennent un attribut nommé foo qui contient respectivement les valeurs entières 6 et 9. Ceci est un exemple très simple. Vos méthodes peuvent utiliser des opérations plus avancées; par exemple, ils pourraient comparer plusieurs attributs différents à la fois.

Opérations mathématiques sur deux objets personnalisés Python

Il est également possible d'effectuer des opérations mathématiques sur des objets personnalisés Python. Suivant la structure de l'extrait précédent sur les méthodes de comparaison, voici un extrait pour effectuer des opérations mathématiques sur deux objets personnalisés Python :

class Value:

    def __init__(self, baz):
        self.baz = baz

    # Adding two objects 
    def __add__(self, obj2):
        return self.baz + obj2.baz

    # Subtracting two objects    
    def __sub__(self, obj2):
        return self.baz - obj2.baz

    # Multiplying two objects    
    def __mul__(self, obj2):
        return self.baz * obj2.baz

    # Dividing two objects    
    def __truediv__(self, obj2):
        return self.baz / obj2.baz

    # Get the remainder of a division of two objects    
    def __mod__(self, obj2):
        return self.baz % obj2.baz
        
foo = Value(2)
bar = Value(4)

print(
    foo + bar,
    foo - bar,
    foo * bar,
    foo / bar,
    foo % bar,
)

Ce qui donne la sortie :

6 -2 8 0.5 2

Pour plus d'informations sur les classes Python, consultez la documentation ici.

Réflexions finales sur la classe personnalisée en Python

Nous avons couvert beaucoup de terrain dans cet article d'introduction sur la création de classes et d'objets personnalisés Python. J'ai choisi de ne pas aborder le sujet des destructeurs, qui sont appelés lorsqu'un objet est détruit. Contrairement à d'autres langages de programmation tels que C++, Python dispose d'un ramasse-miettes avec gestion automatique de la mémoire, ce qui rend les destructeurs moins nécessaires.

C'est un sujet complexe, et nous avons à peine effleuré la surface. Je vous recommande fortement de pratiquer vos compétences Python en jouant avec les extraits ci-dessus et en passant par les exercices de notre piste de programmation Python. Vous pouvez également consulter l'excellente liste de ressources de Dorota pour apprendre Python.

Dans le prochain article, nous explorons comment écrire des modules en Python. En attendant, n'oubliez pas de visiter LearnPython.com !