Intérieurs du modèle d'objet Python

PythonPythonBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Cette section présente plus de détails sur le modèle d'objet interne de Python et discute de certains sujets liés à la gestion de la mémoire, à la copie et à la vérification de type.

Affectation

Plusieurs opérations en Python sont liées à l'affectation ou au stockage de valeurs.

a = valeur         ## Affectation à une variable
s[n] = valeur      ## Affectation à une liste
s.append(valeur)   ## Ajout à une liste
d['clé'] = valeur  ## Ajout à un dictionnaire

Attention : les opérations d'affectation ne font jamais de copie de la valeur affectée. Toutes les affectations ne sont que des copies de référence (ou des copies de pointeur si vous préférez).

Exemple d'affectation

Considérez ce fragment de code.

a = [1,2,3]
b = a
c = [a,b]

Une représentation des opérations mémoire sous-jacentes. Dans cet exemple, il n'y a qu'un seul objet liste [1,2,3], mais quatre références différentes à cet objet.

Diagramme d'exemple de référence mémoire

Cela signifie que modifier une valeur affecte toutes les références.

>>> a.append(999)
>>> a
[1,2,3,999]
>>> b
[1,2,3,999]
>>> c
[[1,2,3,999], [1,2,3,999]]
>>>

Remarquez comment un changement dans la liste d'origine se reflète partout ailleurs (yikes!). C'est parce qu'aucune copie n'a été faite. Tout pointe vers la même chose.

Réaffectation de valeurs

La réaffectation d'une valeur n'écrase jamais la mémoire utilisée par la valeur précédente.

a = [1,2,3]
b = a
a = [4,5,6]

print(a)      ## [4, 5, 6]
print(b)      ## [1, 2, 3]    Conserve la valeur d'origine

Rappelez-vous : Les variables sont des noms, pas des emplacements mémoire.

Certains dangers

Si vous n'êtes pas au courant de ce partage, vous vous piquerez vous-même le pied à un moment donné. Scénario typique. Vous modifiez certaines données en pensant que c'est votre propre copie privée et vous corrompez accidentellement certaines données dans une autre partie du programme.

Commentaire : C'est l'une des raisons pour lesquelles les types de données primitifs (int, float, chaîne) sont immuables (en lecture seule).

Identité et références

Utilisez l'opérateur is pour vérifier si deux valeurs sont exactement le même objet.

>>> a = [1,2,3]
>>> b = a
>>> a is b
True
>>>

is compare l'identité de l'objet (un entier). L'identité peut être obtenue en utilisant id().

>>> id(a)
3588944
>>> id(b)
3588944
>>>

Note : Il est presque toujours préférable d'utiliser == pour vérifier des objets. Le comportement de is est souvent inattendu :

>>> a = [1,2,3]
>>> b = a
>>> c = [1,2,3]
>>> a is b
True
>>> a is c
False
>>> a == c
True
>>>

Copies superficielles

Les listes et les dictionnaires ont des méthodes pour effectuer des copies.

>>> a = [2,3,[100,101],4]
>>> b = list(a) ## Effectuez une copie
>>> a is b
False

C'est une nouvelle liste, mais les éléments de la liste sont partagés.

>>> a[2].append(102)
>>> b[2]
[100,101,102]
>>>
>>> a[2] is b[2]
True
>>>

Par exemple, la liste interne [100, 101, 102] est partagée. Ceci est connu sous le nom de copie superficielle. Voici une illustration.

Copie superficielle

Copies profondes

Parfois, vous avez besoin de créer une copie d'un objet et de tous les objets qu'il contient. Vous pouvez utiliser le module copy pour ce faire :

>>> a = [2,3,[100,101],4]
>>> import copy
>>> b = copy.deepcopy(a)
>>> a[2].append(102)
>>> b[2]
[100,101]
>>> a[2] is b[2]
False
>>>

Noms, valeurs, types

Les noms de variables n'ont pas de type. C'est seulement un nom. Cependant, les valeurs ont un type sous-jacent.

>>> a = 42
>>> b = 'Hello World'
>>> type(a)
<type 'int'>
>>> type(b)
<type 'str'>

type() vous dira de quel type il s'agit. Le nom de type est généralement utilisé comme une fonction qui crée ou convertit une valeur en ce type.

Vérification de type

Comment déterminer si un objet est d'un type spécifique.

if isinstance(a, list):
    print('a est une liste')

Vérification pour l'un de plusieurs types possibles.

if isinstance(a, (list,tuple)):
    print('a est une liste ou un tuple')

*Attention : Ne poussez pas trop loin la vérification de type. Cela peut entraîner une complexité excessive du code. En général, vous ne le feriez que si cela permet de prévenir les erreurs courantes commises par d'autres utilisant votre code.

Tout est un objet

Les nombres, les chaînes de caractères, les listes, les fonctions, les exceptions, les classes, les instances, etc. sont tous des objets. Cela signifie que tous les objets qui peuvent être nommés peuvent être passés comme des données, placés dans des conteneurs, etc., sans aucune restriction. Il n'y a pas de types spéciaux d'objets. Parfois, on dit que tous les objets sont "de première classe".

Un exemple simple :

>>> import math
>>> items = [abs, math, ValueError ]
>>> items
[<fonction intégrée abs>,
  <module'math' (builtin)>,
  <type 'exceptions.ValueError'>]
>>> items[0](-45)
45
>>> items[1].sqrt(2)
1.4142135623730951
>>> try:
        x = int('not a number')
    except items[2]:
        print('Échoué!')
Échoué!
>>>

Ici, items est une liste contenant une fonction, un module et une exception. Vous pouvez directement utiliser les éléments de la liste à la place des noms originaux :

items[0](-45)       ## abs
items[1].sqrt(2)    ## math
except items[2]:    ## ValueError

Avec un grand pouvoir vient une grande responsabilité. Juste parce que vous pouvez le faire ne signifie pas que vous devriez.

Dans cet ensemble d'exercices, nous examinons quelques-uns des pouvoirs issus des objets de première classe.

Exercice 2.24 : Données de première classe

Dans le fichier portfolio.csv, nous lisons des données organisées en colonnes qui ressemblent à ceci :

name,shares,price
"AA",100,32.20
"IBM",50,91.10
...

Dans le code précédent, nous avons utilisé le module csv pour lire le fichier, mais avons encore dû effectuer des conversions de type manuellement. Par exemple :

for row in rows:
    name   = row[0]
    shares = int(row[1])
    price  = float(row[2])

Ce type de conversion peut également être effectué de manière plus astucieuse en utilisant certaines opérations de base de liste.

Créez une liste Python qui contient les noms des fonctions de conversion que vous utiliseriez pour convertir chaque colonne en son type approprié :

>>> types = [str, int, float]
>>>

La raison pour laquelle vous pouvez même créer cette liste est que tout en Python est de première classe. Donc, si vous voulez avoir une liste de fonctions, c'est parfait. Les éléments de la liste que vous avez créé sont des fonctions pour convertir une valeur x en un type donné (par exemple, str(x), int(x), float(x)).

Maintenant, lisez une ligne de données à partir du fichier ci-dessus :

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> row
['AA', '100', '32.20']
>>>

Comme indiqué, cette ligne n'est pas suffisante pour effectuer des calculs car les types sont incorrects. Par exemple :

>>> row[1] * row[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't multiply sequence by non-int of type'str'
>>>

Cependant, peut-être que les données peuvent être associées aux types que vous avez spécifiés dans types. Par exemple :

>>> types[1]
<type 'int'>
>>> row[1]
'100'
>>>

Essayez de convertir l'une des valeurs :

>>> types[1](row[1])     ## Identique à int(row[1])
100
>>>

Essayez de convertir une autre valeur :

>>> types[2](row[2])     ## Identique à float(row[2])
32.2
>>>

Essayez le calcul avec les valeurs converties :

>>> types[1](row[1])*types[2](row[2])
3220.0000000000005
>>>

Associez les types de colonne aux champs et regardez le résultat :

>>> r = list(zip(types, row))
>>> r
[(<type'str'>, 'AA'), (<type 'int'>, '100'), (<type 'float'>,'32.20')]
>>>

Vous remarquerez que cela a associé une conversion de type avec une valeur. Par exemple, int est associé à la valeur '100'.

La liste associée est utile si vous voulez effectuer des conversions sur toutes les valeurs, l'une après l'autre. Essayez ceci :

>>> converted = []
>>> for func, val in zip(types, row):
          converted.append(func(val))
...
>>> converted
['AA', 100, 32.2]
>>> converted[1] * converted[2]
3220.0000000000005
>>>

Assurez-vous de comprendre ce qui se passe dans le code ci-dessus. Dans la boucle, la variable func est l'une des fonctions de conversion de type (par exemple, str, int, etc.) et la variable val est l'une des valeurs telles que 'AA', '100'. L'expression func(val) convertit une valeur (un peu comme un cast de type).

Le code ci-dessus peut être compressé en une seule compréhension de liste.

>>> converted = [func(val) for func, val in zip(types, row)]
>>> converted
['AA', 100, 32.2]
>>>

Exercice 2.25 : Création de dictionnaires

Souvenez-vous de la manière dont la fonction dict() peut facilement créer un dictionnaire si vous avez une séquence de noms de clés et de valeurs? Créons un dictionnaire à partir des en-têtes de colonne :

>>> headers
['name','shares', 'price']
>>> converted
['AA', 100, 32.2]
>>> dict(zip(headers, converted))
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Bien sûr, si vous maîtrisez les compréhensions de liste, vous pouvez effectuer toute la conversion d'un seul coup en utilisant une compréhension de dictionnaire :

>>> { name: func(val) for name, func, val in zip(headers, types, row) }
{'price': 32.2, 'name': 'AA','shares': 100}
>>>

Exercice 2.26 : Le panorama global

En utilisant les techniques de cet exercice, vous pourriez écrire des instructions qui convertissent facilement les champs à partir de presque tout fichier de données orienté colonnes en un dictionnaire Python.

Pour illustrer, supposons que vous lisez des données à partir d'un autre fichier de données comme ceci :

>>> f = open('dowstocks.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> row = next(rows)
>>> headers
['name', 'price', 'date', 'time', 'change', 'open', 'high', 'low', 'volume']
>>> row
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '39.67', '39.69', '39.45', '181800']
>>>

Convertissons les champs en utilisant un truc similaire :

>>> types = [str, float, str, str, float, float, float, float, int]
>>> converted = [func(val) for func, val in zip(types, row)]
>>> record = dict(zip(headers, converted))
>>> record
{'volume': 181800, 'name': 'AA', 'price': 39.48, 'high': 39.69,
'low': 39.45, 'time': '9:36am', 'date': '6/11/2007', 'open': 39.67,
'change': -0.18}
>>> record['name']
'AA'
>>> record['price']
39.48
>>>

Bonus : Comment modifieriez-vous cet exemple pour analyser en outre l'entrée date en un tuple tel que (6, 11, 2007)?

Prenez le temps de réfléchir à ce que vous avez fait dans cet exercice. Nous reviendrons sur ces idées un peu plus tard.

Sommaire

Félicitations! Vous avez terminé le laboratoire sur les objets. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.