Comment utiliser l'attribut __dict__ pour gérer les données d'instance en Python

PythonPythonBeginner
Pratiquer maintenant

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

Introduction

Les fonctionnalités de programmation orientée objet (POO) de Python offrent aux développeurs des outils puissants pour gérer efficacement les données d'instance. L'un de ces outils est l'attribut __dict__, qui vous permet d'accéder et de manipuler dynamiquement les attributs d'un objet Python.

Dans ce tutoriel, nous allons explorer le fonctionnement de l'attribut __dict__ et apprendre différentes façons de l'utiliser pour gérer les données d'instance dans vos projets Python. À la fin de ce lab, vous comprendrez comment tirer parti de cette fonctionnalité pour créer des applications Python plus flexibles et dynamiques.

Comprendre les objets Python et l'attribut __dict__

Commençons par comprendre comment les objets Python stockent leurs attributs et comment nous pouvons y accéder en utilisant l'attribut __dict__.

Qu'est-ce qu'un objet en Python ?

En Python, tout est un objet. Les objets ont des attributs (données) et des méthodes (fonctions). Lorsque vous créez un objet à partir d'une classe, l'objet obtient son propre espace de noms pour stocker ses attributs.

Création d'une classe et d'un objet Python

Créons une classe et un objet Python simples pour commencer à explorer l'attribut __dict__ :

  1. Ouvrez le terminal dans l'environnement LabEx.

  2. Créez un nouveau fichier Python nommé person.py en utilisant l'éditeur de code :

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

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

## Create a Person object
person = Person("Alice", 30)

## Print the person object attributes
print(f"Name: {person.name}")
print(f"Age: {person.age}")
print(f"Greeting: {person.greet()}")

## Let's examine the __dict__ attribute
print("\nThe __dict__ attribute contains:")
print(person.__dict__)
  1. Exécutez le fichier Python dans le terminal :
python3 person.py

Vous devriez voir une sortie similaire à :

Name: Alice
Age: 30
Greeting: Hello, my name is Alice and I am 30 years old.

The __dict__ attribute contains:
{'name': 'Alice', 'age': 30}

Qu'est-ce que l'attribut __dict__ ?

L'attribut __dict__ est un dictionnaire qui contient tous les attributs définis pour un objet. Chaque clé de ce dictionnaire est un nom d'attribut, et chaque valeur est la valeur d'attribut correspondante.

Comme vous pouvez le voir dans la sortie, l'attribut __dict__ de notre objet person contient les attributs name et age que nous avons définis dans la méthode __init__. Cependant, il ne contient pas la méthode greet car les méthodes sont définies dans la classe, et non dans l'instance.

Exploration des attributs de classe et d'instance

Mettons à jour notre code pour comprendre la différence entre les attributs de classe et d'instance :

  1. Modifiez le fichier person.py :
class Person:
    ## Class attribute - shared by all instances
    species = "Human"

    def __init__(self, name, age):
        ## Instance attributes - unique to each instance
        self.name = name
        self.age = age

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

## Create a Person object
person = Person("Alice", 30)

## Print attributes
print(f"Name: {person.name}")
print(f"Age: {person.age}")
print(f"Species: {person.species}")  ## Accessing class attribute

## Examine the __dict__ attributes
print("\nInstance __dict__ contains:")
print(person.__dict__)

print("\nClass __dict__ contains:")
print(Person.__dict__)
  1. Exécutez le fichier mis à jour :
python3 person.py

Vous remarquerez que l'attribut de classe species n'est pas stocké dans le __dict__ de l'instance, mais peut être accessible via l'instance. Le __dict__ de la classe contient tous les attributs et méthodes au niveau de la classe.

Pourquoi __dict__ est-il utile ?

L'attribut __dict__ vous donne un accès direct au mécanisme de stockage sous-jacent des objets Python. Cela peut être utile pour :

  1. Examiner dynamiquement les attributs d'un objet
  2. Ajouter ou modifier des attributs au moment de l'exécution
  3. Sérialiser des objets (les convertir en formats tels que JSON)
  4. Mettre en œuvre des schémas de programmation avancés

Maintenant que vous comprenez ce qu'est __dict__, apprenons à l'utiliser pour manipuler les attributs d'objets à l'étape suivante.

Accéder et modifier les attributs en utilisant __dict__

Maintenant que nous comprenons ce qu'est l'attribut __dict__, apprenons à l'utiliser pour accéder et modifier dynamiquement les attributs d'un objet.

Accéder aux attributs via __dict__

Il existe deux façons d'accéder aux attributs d'un objet en Python :

  1. En utilisant la notation pointée : person.name
  2. En utilisant l'attribut __dict__ : person.__dict__['name']

Créons un nouveau fichier Python pour explorer ces méthodes :

  1. Créez un nouveau fichier nommé attribute_access.py :
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

## Create a Person object
person = Person("Bob", 25)

## Method 1: Using dot notation
print("Using dot notation:")
print(f"Name: {person.name}")
print(f"Age: {person.age}")

## Method 2: Using __dict__
print("\nUsing __dict__:")
print(f"Name: {person.__dict__['name']}")
print(f"Age: {person.__dict__['age']}")

## Print the entire __dict__
print("\nAll attributes:")
print(person.__dict__)
  1. Exécutez le fichier :
python3 attribute_access.py

La sortie devrait montrer que les deux méthodes donnent le même résultat :

Using dot notation:
Name: Bob
Age: 25

Using __dict__:
Name: Bob
Age: 25

All attributes:
{'name': 'Bob', 'age': 25}

Modifier les attributs via __dict__

L'attribut __dict__ ne sert pas seulement à lire les attributs, mais aussi à les modifier ou à en ajouter de nouveaux. Voyons comment :

  1. Créez un nouveau fichier nommé modify_attributes.py :
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

## Create a Person object
person = Person("Charlie", 35)

print("Original attributes:")
print(person.__dict__)

## Modify an existing attribute
person.__dict__['age'] = 36
print("\nAfter modifying age:")
print(person.__dict__)
print(f"Accessing with dot notation: person.age = {person.age}")

## Add a new attribute
person.__dict__['city'] = "New York"
print("\nAfter adding city attribute:")
print(person.__dict__)
print(f"Accessing with dot notation: person.city = {person.city}")

## Delete an attribute
del person.__dict__['city']
print("\nAfter deleting city attribute:")
print(person.__dict__)

## Try to access the deleted attribute (this will cause an error)
try:
    print(person.city)
except AttributeError as e:
    print(f"Error: {e}")
  1. Exécutez le fichier :
python3 modify_attributes.py

Vous devriez voir une sortie similaire à :

Original attributes:
{'name': 'Charlie', 'age': 35}

After modifying age:
{'name': 'Charlie', 'age': 36}
Accessing with dot notation: person.age = 36

After adding city attribute:
{'name': 'Charlie', 'age': 36, 'city': 'New York'}
Accessing with dot notation: person.city = New York

After deleting city attribute:
{'name': 'Charlie', 'age': 36}
Error: 'Person' object has no attribute 'city'

Quand utiliser __dict__ par rapport à la notation pointée

Bien que les deux méthodes puissent être utilisées pour accéder et modifier les attributs, il existe des situations où l'utilisation de __dict__ est plus appropriée :

  1. Lorsque vous devez accéder à un attribut dont le nom est stocké dans une variable
  2. Lorsque vous souhaitez ajouter ou supprimer dynamiquement des attributs
  3. Lorsque vous devez itérer sur tous les attributs d'un objet

Créons un exemple pour illustrer ces cas :

  1. Créez un nouveau fichier nommé dynamic_attributes.py :
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

## Create a Person object
person = Person("David", 40)

## Case 1: Access attribute using a variable name
attr_name = "name"
print(f"Accessing {attr_name}: {person.__dict__[attr_name]}")

## Case 2: Dynamically add attributes
attributes_to_add = {
    'city': 'Boston',
    'job': 'Engineer',
    'salary': 85000
}

for key, value in attributes_to_add.items():
    person.__dict__[key] = value

print("\nAfter adding multiple attributes:")
print(person.__dict__)

## Case 3: Iterate over all attributes
print("\nAll attributes and their values:")
for attr_name, attr_value in person.__dict__.items():
    print(f"{attr_name}: {attr_value}")

## Let's do something practical - create a function to clean up person data
def sanitize_person(person_obj):
    """Remove any attributes that are not name or age"""
    allowed_attrs = ['name', 'age']
    attrs_to_remove = [key for key in person_obj.__dict__ if key not in allowed_attrs]

    for attr in attrs_to_remove:
        del person_obj.__dict__[attr]

sanitize_person(person)
print("\nAfter sanitization:")
print(person.__dict__)
  1. Exécutez le fichier :
python3 dynamic_attributes.py

La sortie montre comment vous pouvez travailler avec les attributs de manière dynamique en utilisant __dict__.

Comparaison des méthodes de manipulation directe des attributs

Maintenant, créons un autre exemple pour comparer les différentes façons de manipuler les attributs :

  1. Créez un nouveau fichier nommé attribute_comparison.py :
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

## Create a Person object
person = Person("Eve", 28)

## Method 1: Using dot notation
person.city = "Chicago"

## Method 2: Using __dict__
person.__dict__['job'] = "Designer"

## Method 3: Using setattr
setattr(person, 'hobby', 'Painting')

## Method 4: Using getattr
name_value = getattr(person, 'name')

print("All attributes after different methods of adding:")
print(person.__dict__)
print(f"Retrieved name using getattr: {name_value}")

## You can also check if an attribute exists
if 'city' in person.__dict__:
    print("The city attribute exists!")

## Or retrieve a value with a default if it doesn't exist
country = person.__dict__.get('country', 'Unknown')
print(f"Country (with default): {country}")
  1. Exécutez le fichier :
python3 attribute_comparison.py

Cet exemple montre qu'il existe plusieurs façons de manipuler les attributs d'objets en Python. Bien que __dict__ vous donne un accès direct au stockage des attributs, il existe d'autres fonctions intégrées comme setattr() et getattr() qui fournissent des fonctionnalités similaires d'une manière plus idiomatique en Python.

Dans l'étape suivante, nous allons explorer quelques applications pratiques de l'utilisation de l'attribut __dict__.

Applications pratiques de __dict__ pour la gestion dynamique des attributs

Maintenant que nous savons comment accéder et modifier les attributs en utilisant __dict__, explorons quelques applications pratiques de cette fonctionnalité dans les programmes Python du monde réel.

Application 1 : Sérialisation d'objets (conversion en JSON)

Un cas d'utilisation courant de __dict__ est la sérialisation d'objets, en particulier lors de la conversion d'objets Python au format JSON pour le stockage ou la transmission.

  1. Créez un nouveau fichier nommé object_serialization.py :
import json

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

    def to_json(self):
        ## Use __dict__ to get all attributes as a dictionary
        return json.dumps(self.__dict__)

    @classmethod
    def from_json(cls, json_str):
        ## Create a new Person object from a JSON string
        data = json.loads(json_str)
        return cls(**data)

## Create a Person object
person = Person("Frank", 45, "San Francisco")

## Serialize to JSON
json_data = person.to_json()
print("JSON Data:")
print(json_data)

## Deserialize from JSON
person2 = Person.from_json(json_data)
print("\nDeserialized Person object attributes:")
print(person2.__dict__)

## Let's create multiple people and serialize them
people = [
    Person("Grace", 32, "Seattle"),
    Person("Henry", 27),
    Person("Isla", 39, "Miami")
]

## Serialize the list of people
people_json = [person.to_json() for person in people]
print("\nJSON for multiple people:")
for p_json in people_json:
    print(p_json)

## Save to a file
with open('people.json', 'w') as f:
    json.dump([p.__dict__ for p in people], f)

print("\nSaved people data to people.json")

## Read from the file
with open('people.json', 'r') as f:
    loaded_data = json.load(f)

print("\nLoaded from file:")
print(loaded_data)

## Convert back to Person objects
loaded_people = [Person(**data) for data in loaded_data]
print("\nRecreated Person objects:")
for person in loaded_people:
    print(f"{person.name}, {person.age}, {getattr(person, 'city', 'No city')}")
  1. Exécutez le fichier :
python3 object_serialization.py

Cet exemple montre comment __dict__ facilite la conversion d'objets Python vers et depuis JSON. En utilisant __dict__, nous pouvons facilement obtenir tous les attributs d'un objet sous forme de dictionnaire, qui peut ensuite être converti en JSON à l'aide du module json.

Application 2 : Fabrique d'objets dynamique

Une autre application pratique de __dict__ est la création d'objets dynamiquement en fonction des données :

  1. Créez un nouveau fichier nommé dynamic_object_factory.py :
class DynamicObject:
    def __init__(self, **kwargs):
        ## Add all the keyword arguments as attributes
        for key, value in kwargs.items():
            self.__dict__[key] = value

    def __str__(self):
        attributes = ", ".join(f"{k}={v}" for k, v in self.__dict__.items())
        return f"DynamicObject({attributes})"

## Create objects with different attributes
person = DynamicObject(name="Jennifer", age=29, profession="Developer")
car = DynamicObject(make="Toyota", model="Camry", year=2020, color="Blue")
book = DynamicObject(title="Python Programming", author="John Smith", pages=350)

## Print the objects
print(person)
print(car)
print(book)

## We can add attributes after creation
person.__dict__['country'] = "Canada"
print("\nAfter adding country attribute:")
print(person)

## We can also create an empty object and fill it later
empty_obj = DynamicObject()
print("\nEmpty object:", empty_obj)

## Fill it with data from a dictionary
data = {"type": "Laptop", "brand": "Dell", "ram": "16GB", "storage": "512GB SSD"}
empty_obj.__dict__.update(data)
print("After filling:", empty_obj)

## Let's create a factory function that creates objects from different data sources
def create_object_from_data(data_source):
    if isinstance(data_source, dict):
        return DynamicObject(**data_source)
    elif isinstance(data_source, list) and all(isinstance(item, tuple) and len(item) == 2 for item in data_source):
        return DynamicObject(**dict(data_source))
    else:
        raise ValueError("Unsupported data source type")

## Create objects from different data sources
dict_data = {"name": "Kevin", "age": 35, "email": "[email protected]"}
list_data = [("product", "Monitor"), ("price", 299.99), ("in_stock", True)]

obj1 = create_object_from_data(dict_data)
obj2 = create_object_from_data(list_data)

print("\nObjects created from different data sources:")
print(obj1)
print(obj2)
  1. Exécutez le fichier :
python3 dynamic_object_factory.py

Cet exemple montre comment nous pouvons utiliser __dict__ pour créer des objets dynamiques avec des attributs arbitraires, ce qui est utile lorsque l'on travaille avec des données provenant de sources externes telles que des API, des fichiers ou des bases de données.

Application 3 : Suivi simple des attributs

Nous pouvons utiliser __dict__ pour suivre les modifications apportées aux attributs d'un objet, ce qui peut être utile pour des fonctionnalités telles que la détection des modifications ou la mise en œuvre de la fonctionnalité annuler/rétablir :

  1. Créez un nouveau fichier nommé attribute_tracking.py :
class TrackedObject:
    def __init__(self, **kwargs):
        ## Initialize with the provided attributes
        self.__dict__.update(kwargs)
        ## Store the original state
        self.__original_state = self.__dict__.copy()

    def get_changes(self):
        """Return a dictionary of attributes that have changed"""
        changes = {}
        for key, current_value in self.__dict__.items():
            ## Skip the original state attribute itself
            if key == '_TrackedObject__original_state':
                continue

            ## Check if the attribute existed originally
            if key in self.__original_state:
                ## Check if the value has changed
                if current_value != self.__original_state[key]:
                    changes[key] = {
                        'old': self.__original_state[key],
                        'new': current_value
                    }
            else:
                ## This is a new attribute
                changes[key] = {
                    'old': None,
                    'new': current_value
                }

        ## Check for deleted attributes
        for key in self.__original_state:
            if key not in self.__dict__:
                changes[key] = {
                    'old': self.__original_state[key],
                    'new': None
                }

        return changes

    def has_changes(self):
        """Check if the object has any changes"""
        return len(self.get_changes()) > 0

    def reset(self):
        """Reset the object to its original state"""
        ## Remove all current attributes
        for key in list(self.__dict__.keys()):
            if key != '_TrackedObject__original_state':
                del self.__dict__[key]

        ## Add back the original attributes
        for key, value in self.__original_state.items():
            self.__dict__[key] = value

## Create a tracked object
user = TrackedObject(name="Linda", email="[email protected]", age=31)

## Print the original state
print("Original state:")
print(user.__dict__)

## Make some changes
user.age = 32
user.email = "[email protected]"
user.address = "123 Main St"
del user.name

## Check for changes
print("\nAfter changes:")
print(user.__dict__)

print("\nDetected changes:")
changes = user.get_changes()
for attr, change in changes.items():
    print(f"{attr}: {change['old']} -> {change['new']}")

print(f"\nHas changes: {user.has_changes()}")

## Reset to original state
user.reset()
print("\nAfter reset:")
print(user.__dict__)
print(f"Has changes: {user.has_changes()}")
  1. Exécutez le fichier :
python3 attribute_tracking.py

Cet exemple montre comment nous pouvons utiliser __dict__ pour implémenter le suivi des attributs, ce qui peut être utile dans de nombreuses applications, telles que la validation de formulaires, la gestion d'état ou la mise en œuvre de la fonctionnalité annuler/rétablir.

L'attribut __dict__ est un outil puissant dans l'arsenal de la programmation orientée objet de Python. En comprenant son fonctionnement et comment l'utiliser efficacement, vous pouvez créer du code Python plus flexible, dynamique et maintenable.

Création d'un mini-projet : Gestionnaire de contacts utilisant __dict__

Maintenant que nous avons exploré diverses applications de l'attribut __dict__, mettons nos connaissances en pratique en créant une simple application de gestionnaire de contacts. Ce mini-projet démontrera comment utiliser __dict__ dans un scénario réel.

L'application de gestionnaire de contacts

Notre gestionnaire de contacts nous permettra de :

  1. Ajouter des contacts avec divers attributs
  2. Rechercher des contacts
  3. Mettre à jour les informations de contact
  4. Supprimer des contacts
  5. Exporter les contacts en JSON
  6. Importer les contacts depuis JSON

Étape 1 : Créer les classes Contact et ContactManager

  1. Créez un nouveau fichier nommé contact_manager.py :
import json
import os

class Contact:
    def __init__(self, name, email=None, phone=None, **kwargs):
        self.name = name
        self.email = email
        self.phone = phone

        ## Add any additional attributes
        for key, value in kwargs.items():
            self.__dict__[key] = value

    def update(self, **kwargs):
        """Update contact attributes"""
        self.__dict__.update(kwargs)

    def __str__(self):
        """String representation of the contact"""
        attrs = []
        for key, value in self.__dict__.items():
            if value is not None:
                attrs.append(f"{key}: {value}")
        return ", ".join(attrs)


class ContactManager:
    def __init__(self):
        self.contacts = []

    def add_contact(self, contact):
        """Add a new contact"""
        self.contacts.append(contact)
        print(f"Added contact: {contact.name}")

    def find_contact(self, **kwargs):
        """Find contacts matching the criteria"""
        results = []

        for contact in self.contacts:
            match = True
            for key, value in kwargs.items():
                ## Skip if the contact doesn't have this attribute
                if key not in contact.__dict__:
                    match = False
                    break

                ## Skip if the attribute value doesn't match
                if contact.__dict__[key] != value:
                    match = False
                    break

            if match:
                results.append(contact)

        return results

    def update_contact(self, contact, **kwargs):
        """Update a contact's attributes"""
        contact.update(**kwargs)
        print(f"Updated contact: {contact.name}")

    def delete_contact(self, contact):
        """Delete a contact"""
        if contact in self.contacts:
            self.contacts.remove(contact)
            print(f"Deleted contact: {contact.name}")
        else:
            print("Contact not found.")

    def export_contacts(self, filename):
        """Export contacts to a JSON file"""
        contacts_data = []
        for contact in self.contacts:
            contacts_data.append(contact.__dict__)

        with open(filename, 'w') as f:
            json.dump(contacts_data, f, indent=2)

        print(f"Exported {len(self.contacts)} contacts to {filename}")

    def import_contacts(self, filename):
        """Import contacts from a JSON file"""
        if not os.path.exists(filename):
            print(f"File {filename} not found.")
            return

        with open(filename, 'r') as f:
            contacts_data = json.load(f)

        imported_count = 0
        for data in contacts_data:
            ## Create a copy of the data to avoid modifying the original
            contact_data = data.copy()

            ## Get the required parameters
            name = contact_data.pop('name', None)
            email = contact_data.pop('email', None)
            phone = contact_data.pop('phone', None)

            if name:
                ## Create a new contact with remaining attributes as kwargs
                contact = Contact(name, email, phone, **contact_data)
                self.contacts.append(contact)
                imported_count += 1

        print(f"Imported {imported_count} contacts from {filename}")

    def print_all_contacts(self):
        """Print all contacts"""
        if not self.contacts:
            print("No contacts found.")
            return

        print(f"\nAll Contacts ({len(self.contacts)}):")
        print("-" * 40)
        for i, contact in enumerate(self.contacts, 1):
            print(f"{i}. {contact}")
        print("-" * 40)


## Let's test our contact manager
if __name__ == "__main__":
    ## Create a contact manager
    manager = ContactManager()

    ## Add some contacts
    manager.add_contact(Contact("John Doe", "[email protected]", "555-1234",
                               address="123 Main St", city="Boston"))

    manager.add_contact(Contact("Jane Smith", "[email protected]", "555-5678",
                               company="ABC Corp", role="Developer"))

    manager.add_contact(Contact("Bob Johnson", "[email protected]", "555-9012",
                               twitter="@bobjohnson", birthday="1985-03-15"))

    ## Print all contacts
    manager.print_all_contacts()

    ## Find contacts
    print("\nContacts with email ending with @example.com:")
    for contact in manager.contacts:
        if contact.email and contact.email.endswith("@example.com"):
            print(f"- {contact.name}: {contact.email}")

    ## Use the find_contact method
    print("\nFinding contacts by name:")
    results = manager.find_contact(name="Jane Smith")
    for contact in results:
        print(f"Found: {contact}")

    ## Update a contact
    if results:
        manager.update_contact(results[0], phone="555-NEW-NUM", role="Senior Developer")
        print(f"After update: {results[0]}")

    ## Export contacts to JSON
    manager.export_contacts("contacts.json")

    ## Delete a contact
    manager.delete_contact(results[0])

    ## Print all contacts after deletion
    manager.print_all_contacts()

    ## Create a new manager and import contacts
    print("\nCreating a new manager and importing contacts:")
    new_manager = ContactManager()
    new_manager.import_contacts("contacts.json")
    new_manager.print_all_contacts()
  1. Exécutez le fichier :
python3 contact_manager.py

Vous devriez voir une sortie montrant le gestionnaire de contacts en action, y compris l'ajout, la recherche, la mise à jour et la suppression de contacts, ainsi que l'exportation et l'importation de contacts vers et depuis un fichier JSON.

Étape 2 : Étendre le gestionnaire de contacts avec des fonctionnalités personnalisées

Maintenant, améliorons notre gestionnaire de contacts en ajoutant la possibilité d'ajouter des champs personnalisés pour différents types de contacts :

  1. Créez un nouveau fichier nommé extended_contact_manager.py :
from contact_manager import Contact, ContactManager

class BusinessContact(Contact):
    def __init__(self, name, email=None, phone=None, company=None, role=None, **kwargs):
        super().__init__(name, email, phone, **kwargs)
        self.company = company
        self.role = role
        self.contact_type = "business"

class PersonalContact(Contact):
    def __init__(self, name, email=None, phone=None, relationship=None, birthday=None, **kwargs):
        super().__init__(name, email, phone, **kwargs)
        self.relationship = relationship
        self.birthday = birthday
        self.contact_type = "personal"

class ExtendedContactManager(ContactManager):
    def add_business_contact(self, name, email=None, phone=None, company=None, role=None, **kwargs):
        contact = BusinessContact(name, email, phone, company, role, **kwargs)
        self.add_contact(contact)
        return contact

    def add_personal_contact(self, name, email=None, phone=None, relationship=None, birthday=None, **kwargs):
        contact = PersonalContact(name, email, phone, relationship, birthday, **kwargs)
        self.add_contact(contact)
        return contact

    def find_by_contact_type(self, contact_type):
        """Find contacts by type (business or personal)"""
        return self.find_contact(contact_type=contact_type)

    def get_contact_details(self, contact):
        """Get detailed information about a contact"""
        details = []
        for key, value in contact.__dict__.items():
            if value is not None:
                if key == "contact_type":
                    details.append(f"Type: {value.capitalize()}")
                else:
                    ## Convert key from snake_case to Title Case
                    formatted_key = " ".join(word.capitalize() for word in key.split("_"))
                    details.append(f"{formatted_key}: {value}")

        return "\n".join(details)

## Test the extended contact manager
if __name__ == "__main__":
    ## Create an extended contact manager
    manager = ExtendedContactManager()

    ## Add some business contacts
    manager.add_business_contact(
        "Alice Johnson",
        "[email protected]",
        "555-1111",
        "XYZ Corp",
        "Marketing Manager",
        department="Marketing",
        office_location="Building A, 3rd Floor"
    )

    manager.add_business_contact(
        "Bob Williams",
        "[email protected]",
        "555-2222",
        "StartUp Inc",
        "CEO",
        linkedin="linkedin.com/in/bobwilliams"
    )

    ## Add some personal contacts
    manager.add_personal_contact(
        "Carol Davis",
        "[email protected]",
        "555-3333",
        "Friend",
        "1990-05-15",
        address="456 Oak St",
        favorite_restaurant="Italian Place"
    )

    manager.add_personal_contact(
        "Dave Wilson",
        "[email protected]",
        "555-4444",
        "Family",
        "1982-12-03",
        emergency_contact=True
    )

    ## Print all contacts
    manager.print_all_contacts()

    ## Find contacts by type
    print("\nBusiness Contacts:")
    business_contacts = manager.find_by_contact_type("business")
    for contact in business_contacts:
        print(f"- {contact.name} ({contact.company})")

    print("\nPersonal Contacts:")
    personal_contacts = manager.find_by_contact_type("personal")
    for contact in personal_contacts:
        print(f"- {contact.name} ({contact.relationship})")

    ## Show detailed information for a contact
    if business_contacts:
        print("\nDetailed information for", business_contacts[0].name)
        print(manager.get_contact_details(business_contacts[0]))

    ## Export contacts to JSON
    manager.export_contacts("extended_contacts.json")

    ## Import contacts
    new_manager = ExtendedContactManager()
    new_manager.import_contacts("extended_contacts.json")
    print("\nAfter importing:")
    new_manager.print_all_contacts()

    ## Check if we can still identify contact types after import
    imported_business = new_manager.find_by_contact_type("business")
    print(f"\nImported {len(imported_business)} business contacts")

    imported_personal = new_manager.find_by_contact_type("personal")
    print(f"Imported {len(imported_personal)} personal contacts")
  1. Exécutez le fichier :
python3 extended_contact_manager.py

Ce gestionnaire de contacts étendu montre comment nous pouvons utiliser l'attribut __dict__ pour créer des structures de données flexibles capables de gérer différents types de contacts avec des attributs variables.

Étape 3 : Créer une simple interface en ligne de commande

Enfin, créons une simple interface en ligne de commande pour notre gestionnaire de contacts :

  1. Créez un nouveau fichier nommé contact_manager_cli.py :
from extended_contact_manager import ExtendedContactManager, BusinessContact, PersonalContact

def print_menu():
    print("\n===== Contact Manager =====")
    print("1. Add Business Contact")
    print("2. Add Personal Contact")
    print("3. List All Contacts")
    print("4. Find Contact")
    print("5. Update Contact")
    print("6. Delete Contact")
    print("7. Export Contacts")
    print("8. Import Contacts")
    print("9. Exit")
    print("==========================")

def get_contact_details(contact_type):
    """Get contact details from user input"""
    details = {}

    ## Common fields
    details['name'] = input("Name: ")
    details['email'] = input("Email (optional): ") or None
    details['phone'] = input("Phone (optional): ") or None

    ## Type-specific fields
    if contact_type == "business":
        details['company'] = input("Company (optional): ") or None
        details['role'] = input("Role (optional): ") or None

        ## Ask for custom fields
        print("Add custom fields (leave empty to finish):")
        while True:
            field_name = input("Field name (or empty to finish): ")
            if not field_name:
                break
            field_value = input(f"{field_name}: ")
            details[field_name] = field_value

    elif contact_type == "personal":
        details['relationship'] = input("Relationship (optional): ") or None
        details['birthday'] = input("Birthday (YYYY-MM-DD, optional): ") or None

        ## Ask for custom fields
        print("Add custom fields (leave empty to finish):")
        while True:
            field_name = input("Field name (or empty to finish): ")
            if not field_name:
                break
            field_value = input(f"{field_name}: ")
            details[field_name] = field_value

    return details

def select_contact(manager):
    """Let the user select a contact from the list"""
    if not manager.contacts:
        print("No contacts available.")
        return None

    print("\nSelect a contact:")
    for i, contact in enumerate(manager.contacts, 1):
        print(f"{i}. {contact.name}")

    try:
        selection = int(input("Enter number (0 to cancel): "))
        if selection == 0:
            return None
        if 1 <= selection <= len(manager.contacts):
            return manager.contacts[selection - 1]
        else:
            print("Invalid selection.")
            return None
    except ValueError:
        print("Please enter a valid number.")
        return None

def main():
    manager = ExtendedContactManager()

    while True:
        print_menu()
        choice = input("Enter your choice (1-9): ")

        if choice == '1':
            ## Add Business Contact
            print("\n-- Add Business Contact --")
            details = get_contact_details("business")
            name = details.pop('name')
            email = details.pop('email')
            phone = details.pop('phone')
            company = details.pop('company')
            role = details.pop('role')
            manager.add_business_contact(name, email, phone, company, role, **details)

        elif choice == '2':
            ## Add Personal Contact
            print("\n-- Add Personal Contact --")
            details = get_contact_details("personal")
            name = details.pop('name')
            email = details.pop('email')
            phone = details.pop('phone')
            relationship = details.pop('relationship')
            birthday = details.pop('birthday')
            manager.add_personal_contact(name, email, phone, relationship, birthday, **details)

        elif choice == '3':
            ## List All Contacts
            manager.print_all_contacts()

        elif choice == '4':
            ## Find Contact
            print("\n-- Find Contact --")
            search_term = input("Enter name to search: ")
            results = manager.find_contact(name=search_term)

            if results:
                print(f"\nFound {len(results)} contacts:")
                for contact in results:
                    print(manager.get_contact_details(contact))
                    print("-" * 30)
            else:
                print("No contacts found with that name.")

        elif choice == '5':
            ## Update Contact
            print("\n-- Update Contact --")
            contact = select_contact(manager)
            if contact:
                print("\nCurrent details:")
                print(manager.get_contact_details(contact))

                print("\nEnter new details (leave empty to keep current value):")
                updates = {}

                for key, value in contact.__dict__.items():
                    if key != "contact_type":  ## Don't allow changing the contact type
                        new_value = input(f"{key} [{value}]: ")
                        if new_value and new_value != str(value):
                            updates[key] = new_value

                manager.update_contact(contact, **updates)
                print("\nContact updated.")

        elif choice == '6':
            ## Delete Contact
            print("\n-- Delete Contact --")
            contact = select_contact(manager)
            if contact:
                confirm = input(f"Are you sure you want to delete {contact.name}? (y/n): ")
                if confirm.lower() == 'y':
                    manager.delete_contact(contact)

        elif choice == '7':
            ## Export Contacts
            print("\n-- Export Contacts --")
            filename = input("Enter filename (default: contacts.json): ") or "contacts.json"
            manager.export_contacts(filename)

        elif choice == '8':
            ## Import Contacts
            print("\n-- Import Contacts --")
            filename = input("Enter filename: ")
            manager.import_contacts(filename)

        elif choice == '9':
            ## Exit
            print("\nThank you for using Contact Manager!")
            break

        else:
            print("Invalid choice. Please try again.")

if __name__ == "__main__":
    main()
  1. Exécutez l'application CLI :
python3 contact_manager_cli.py
  1. Essayez d'ajouter des contacts, de rechercher des contacts, de mettre à jour des contacts et d'exporter/importer des contacts à l'aide de l'interface en ligne de commande.

Ce mini-projet démontre à quel point l'attribut __dict__ peut être puissant lors de la création d'applications pilotées par les données et flexibles en Python. Le gestionnaire de contacts permet des champs personnalisés sur les contacts, la sérialisation vers et depuis JSON, et une gestion facile des différents types de contacts, en tirant tous parti de l'attribut __dict__ pour gérer dynamiquement les données d'instance.

Grâce à ce projet, vous avez appris à :

  • Utiliser __dict__ pour stocker et récupérer les attributs d'objets
  • Créer des classes flexibles capables de gérer des attributs variables
  • Sérialiser et désérialiser des objets vers et depuis JSON
  • Créer une simple application en ligne de commande qui exploite les attributs dynamiques

Ces compétences peuvent être appliquées à de nombreuses applications Python réelles, des outils de traitement de données aux applications Web et aux intégrations d'API.

Résumé

Dans ce laboratoire, vous avez exploré le puissant attribut __dict__ en Python et appris comment il peut être utilisé pour gérer efficacement les données d'instance. Voici un récapitulatif de ce que vous avez appris :

  1. Comprendre __dict__ : Vous avez appris que l'attribut __dict__ est un dictionnaire qui stocke les variables d'instance d'un objet, offrant ainsi un moyen d'accéder et de manipuler dynamiquement les attributs d'un objet.

  2. Accéder et modifier les attributs : Vous avez découvert diverses façons d'accéder et de modifier les attributs d'un objet, notamment la notation par point, la manipulation directe de __dict__ et les fonctions intégrées telles que setattr() et getattr().

  3. Applications pratiques : Vous avez exploré des applications pratiques de __dict__, notamment la sérialisation d'objets, la gestion dynamique des attributs et le suivi des attributs.

  4. Création d'un mini-projet : Vous avez mis vos connaissances en pratique en créant une application de gestionnaire de contacts qui exploite l'attribut __dict__ pour le stockage flexible des données, la sérialisation et la gestion dynamique des attributs.

L'attribut __dict__ est un outil puissant qui peut vous aider à écrire du code Python plus flexible et dynamique. En comprenant son fonctionnement et comment l'utiliser efficacement, vous pouvez créer des applications capables de s'adapter aux exigences changeantes et de gérer facilement des structures de données diverses.

Au fur et à mesure que vous poursuivez votre parcours Python, n'oubliez pas que, bien que l'attribut __dict__ offre une grande flexibilité, il doit être utilisé avec discernement. Dans de nombreux cas, des approches plus idiomatiques de Python, telles que l'utilisation de propriétés, de descripteurs ou de fonctions intégrées comme getattr() et setattr(), peuvent fournir des solutions plus propres et plus faciles à maintenir.