Python >> Tutoriel Python >  >> Python

Le trait de soulignement en Python ["_" vs "__"]

Le trait de soulignement unique en Python "_" est utilisé pour rendre une variable différente d'un mot-clé Python comme dans float_=8 , ou pour indiquer qu'il doit être utilisé dans un contexte privé comme dans _var=8 .

>>> _str = 'hello'
>>> _str
'hello'

Le double trait de soulignement en Python "__" (appelé dunder " ) est utilisé pour rendre privé un attribut ou une méthode d'instance auquel il est impossible d'accéder depuis l'extérieur de la classe) lorsqu'il est utilisé comme préfixe dunder principal. Lorsqu'il est utilisé comme dunder englobant comme dans "__init__" , cela indique qu'il s'agit d'une méthode spéciale en Python appelée "méthode magique" .

>>> class Wizard:
	def __init__(self):
		self.mana = 100

		
>>> harry = Wizard()
>>> harry.mana
100

Dans cet article et la vidéo bonus, vous apprendrez les tenants et les aboutissants du trait de soulignement en Python.

Les définitions de classe peuvent être intimidantes. Parfois, si vous lisez les exemples de code simplifiés des didacticiels Python, vous pouvez croire que vous obtenez le didacticiel. Mais écrire et lire du code dans le monde réel peut devenir moche très rapidement. Par exemple, regardez l'extrait de code suivant d'un didacticiel Python en ligne :

class Length:

    __metric = {"mm" : 0.001, "cm" : 0.01, "m" : 1, "km" : 1000,
                "in" : 0.0254, "ft" : 0.3048, "yd" : 0.9144,
                "mi" : 1609.344 }
    
    def __init__(self, value, unit = "m" ):
        self.value = value
        self.unit = unit
    
    def Converse2Metres(self):
        return self.value * Length.__metric[self.unit]
    
    def __add__(self, other):
        l = self.Converse2Metres() + other.Converse2Metres()
        return Length(l / Length.__metric[self.unit], self.unit )
    
    def __str__(self):
        return str(self.Converse2Metres())
    
    def __repr__(self):
        return "Length(" + str(self.value) + ", '" + self.unit + "')"

if __name__ == "__main__":
    x = Length(4)
    print(x)
    y = eval(repr(x))

    z = Length(4.5, "yd") + Length(1)
    print(repr(z))
    print(z)

Si vous n'avez pas déjà investi du temps, des efforts et de l'argent dans une formation Python performante, vous pouvez vous demander :Quel est le problème avec ce code ?

Après vous être calmé, vous vous rendrez peut-être compte que la principale chose que vous n'obtenez pas est l'utilisation du "trait de soulignement" en Python. Dans l'extrait de code, les traits de soulignement semblent être partout !

Ce qui est triste, c'est que si vous ne comprenez pas la signification du trait de soulignement, vous ne pourrez jamais suivre des discussions Python approfondies, vous ne comprendrez pas beaucoup de discussions Python, sans parler des bases de code des autres.

Il est vital pour votre carrière - et pour votre capacité à communiquer en Python - que vous étudiiez à fond ces connaissances apparemment infimes.

Quelle est la signification du trait de soulignement dans Python Object Orientation ?

Le trait de soulignement unique n'a, en fait, aucune signification particulière.

  • Vous pouvez utiliser le trait de soulignement pour séparer des mots comme dans :this_is_a_long_variable = 42 .
  • Si vous commencez un nom d'un attribut d'instance avec le trait de soulignement comme dans _var = 8 , vous indiquez que cet attribut d'instance est censé être "privé" et qu'il ne doit pas être accessible depuis l'extérieur de la classe. Cependant, cela reste possible, comme vous pouvez le voir dans l'extrait de code suivant :
class Wizard:


    # underscore = visual separator
    studied_at = "Hogwarts"

    # underscore = "please keep me private"
    _wizards = []


# discouraged but possible:
print(Wizard._wizards)
# []

print(Wizard.studied_at)
# Hogwarts

Le double trait de soulignement (également appelé "dunder" par les pros de Python) a en revanche une signification particulière.

Il y a deux cas :

  1. Dunders en tête :__var
  2. englobant les dunders :__init__

Leader Dunders

Lors du démarrage d'un nom avec dunders , vous demandez à l'interpréteur Python de protéger son utilisation de l'extérieur de la classe. La méthode ou l'attribut ainsi protégé n'est pas accessible directement de l'extérieur (c'est toujours possible mais vous devez activement contourner la protection).

Voici un exemple d'Harry Potter qui montre la nature privée d'un chef de file :

class Wizard:


    # underscore = visual separator
    studied_at = "Hogwarts"

    # underscore = "please keep me private"
    _wizards = []

    # enclosing dunder = magic methods
    def __init__(self, mana):

        self.mana = mana
        Wizard._wizards.append(self)

        # trailing underscore = overwrite keyword
        self.key_ = True
        
        # leading dunder = "enforce to keep me private"
        self.__supersecretphrase = "wingardium leviosa"

    def secret_trick(self):
        return self.__supersecretphrase

tom = Wizard(100)
print(tom.__supersecretphrase)
# AttributeError: 'Wizard' object has no attribute '__supersecretphrase'

print(tom.secret_trick())
# wingardium leviosa

Vous ne pouvez pas accéder à l'attribut d'instance __supersecretphrase de l'extérieur de la classe ! L'interpréteur Python générera une erreur si vous essayez de le faire de manière aussi directe. Mais vous pouvez le faire en appelant la méthode non privée secret_trick() qui accède à l'attribut d'instance privé __supersecretphrase depuis la définition de classe.

Maintenant, vous pouvez demander :

Pour quelle raison protège-t-on les noms de cette manière ?

Le paradigme de la programmation orientée objet découle de l'idée d'« encapsulation ». Chaque objet encapsule des données (les attributs d'instance) et des méthodes pour accéder aux données (les méthodes d'instance). Le point de vue le plus extrême consiste à interdire complètement la modification des attributs de l'instance depuis l'extérieur. Cela conduit à une sémantique très claire (quel est l'effet de vos objets) et cache la complexité à l'utilisateur de votre classe.

Enfermer les Dunders

Lorsque vous entourez un nom de méthode avec dunders comme __init__ , vous indiquez qu'il s'agit d'une méthode spéciale. Les pros l'appellent en fait "méthode magique" en Python - un terme qui convient très bien à notre exemple ;).

Vous avez probablement déjà utilisé le spécial __init_ _ méthode (le constructeur) assez lourdement pour créer de nouvelles instances à partir d'une description de classe.

Mais il existe aussi de nombreuses autres méthodes spéciales. Un exemple est le __str__ méthode qui vous permet de créer une nouvelle représentation textuelle de votre objet.

Voici un exemple qui montre comment la classe Wizard2 remplace la représentation sous forme de chaîne par défaut à l'aide de la méthode dunder englobante __str__ :

class Wizard1:
    def __init__(self, mana):
        self.mana = mana


class Wizard2(Wizard1):
    def __str__(self):
        return "Wizard's Mana Level: " + str(self.mana)


tom = Wizard1(99)
print(tom)
# <__main__.Wizard1 object at 0x000001FEFF2ACA90>

harry = Wizard2(101)
print(harry)
# Wizard's Mana Level: 101

The class Wizard1 is the top-level class here. It defines the constructor using the magic method __init__.

La classe Wizard1 est la classe de niveau supérieur ici. Il définit le constructeur en utilisant la méthode magique __init__ .

La classe Wizard2 est une classe qui hérite de la classe de niveau supérieur (vous pouvez en savoir plus sur l'héritage ici). En d'autres termes, Wizard2 "hérite" de toutes les méthodes et attributs de la classe parente Wizard1 .

Mais en plus de cela, Wizard2 définit également le __str__ méthode qui renvoie une représentation textuelle de l'instance courante sur laquelle elle est appelée.

Lors de l'impression du Wizard1 instance (par exemple tom ), la représentation textuelle par défaut est vraiment moche. Il ne vous donne que le code hexadécimal de l'objet — pas vraiment utile pour comprendre l'instance. Mais lors de l'impression du Wizard2 instance (par exemple harry ), Python appellera implicitement votre __str__ défini méthode et renvoie la représentation textuelle telle que définie par vous.

Il existe de nombreuses autres méthodes magiques. Par exemple, vous pouvez remplacer le comportement par défaut pour l'addition, la soustraction, la multiplication et la division :

class Wizard:

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


    def __add__(self, other):
        return Wizard(self.mana + other.mana)


tom = Wizard(99)
harry = Wizard(101)
print((tom+harry).mana)
# 200

Dans l'exemple, l'addition de deux assistants crée un nouvel assistant qui a l'additif mana des deux assistants.