Skip to content

Solution du problème ChangeDirectory

Vous trouverez ici la solution pour l'exercice suivant :

ChangeDirectory

Exercice de base

Alle hop, on commence directe. Pour créer un context manager il faut une classe qui implemente les deux dunder functions suivantes: __enter__ et __exit__

Note

dunder functions est le nom des fonctions qui commencent par un double underscore: Double UNDERscore => dunder . Pour les recherches dans les moteurs de recherche, cela peut grandement aider d'avoir les bons noms ;).

Notre context manager va donc ressembler a quelque chose comme cela:

class cd(object):
    def __init__(self, new_cwd):
        pass

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, exc_traceback):
        pass

Il va falloir conserver le cwd (repertoire de travail courant: current working directory) afin de pouvoir revenir dans ce repertoire dans la partie __exit__. Pour l'obtenir, rien de plus simple os.getcwd() nous le donne.

Il faudra aussi changer de repertoire courant, et pour cela il faudra utiliser os.chdir().

import os

class cd(object):
    def __init__(self, new_cwd):
        self.new_cwd = new_cwd
        self.old_cwd = None

    def __enter__(self):
        self.old_cwd = os.getcwd()
        os.chdir(self.new_cwd)

    def __exit__(self, exc_type, exc_value, exc_traceback):
        os.chdir(self.old_cwd)

Et voila, 10 lignes de code, qui pourront être réutilisees dans plein de projets, pour 1 ligne de code client:

...
with cd("/tmp/"):
    client code

Bonus 1

Il faut ajouter la possibilite de gerer un repertoire temporaire créé pour l'occasion. Créer un repertoire temporaire est très simple avec le module dédié tmpfile, la fonction mkdtemp() crée un répertoire temporaire dont le client aura la résponsabilité de le supprimer.

Comment le faire de manière optionnelle? Il faut donner une valeure par défaut au parametre, ainsi, si le client ne donne pas de valeure, la valeure prise sera celle par défaut.

Le mieux pour cette valeure par défaut sera d'utiliser la valeure spéciale None. Quand nous entrerons dans le context manager, il faudra faire en sorte de tester si la valeure est celle par défaut, et dans ce cas, il faudra creer le répertoire temporaire.

Pour supprimer un repertoire, il suffira d'utiliser la fonction rmtree() du module shutil. Il faudra le faire en sortant du context (dans __exit__).

import os
import shutil
import tempfile

class cd(object):
    def __init__(self, new_cwd=None):
        self.new_cwd = new_cwd
        self.old_cwd = None
        self.temporary_directory = None

    def __enter__(self):
        self.old_cwd = os.getcwd()
        if self.new_cwd is None:
            # create a temporary directory
            self.temporary_directory = tempfile.mkdtemp()
            os.chdir(self.temporary_directory)
        else:
            os.chdir(self.new_cwd)

    def __exit__(self, exc_type, exc_value, exc_traceback):
        os.chdir(self.old_cwd)
        if self.temporary_directory:
            shutil.rmtree(self.temporary_directory)

Bonus 2

Afin de rendre le context compatible avec la syntax with...as il suffit simplement de faire en sorte que la méthode __enter__ retourne le context lui même.

...
    def __enter__(self):
        self.old_cwd = os.getcwd()
        if self.new_cwd is None:
            # create a temporary directory
            self.temporary_directory = tempfile.mkdtemp()
            os.chdir(self.temporary_directory)
        else:
            os.chdir(self.new_cwd)
        return self

Bonus 3

Il va falloir faire en sorte que notre context puisse fonctionner aussi même hors d'un bloque with.... Afin d'y arriver, il suffit de rajouter les deux fonctions données dans l'ennoncé. Et comme nous sommes assez fénéants en informatique, nous allons réutiliser les fonctions que l'on a déjà ecrites. La fénéantise n'est pas la seule raison (quoique), la vraie: réutiliser du code permet de ne pas dupliquer des lignes de code qui peuvent contenir des erreurs. Ainsi, si on a le code en double, une correction d'un cote peut ne pas etre faite de l'autre. Alors que si on réutilise autant que possible, on aura donc tous les chemins possible corrigés.

enter pourra reutiliser la fonciton __enter__, et leave pourra quand a elle réutiliser __exit__ avec des parametres neutres.

Les voila:

    def enter(self):
        self.__enter__()

    def leave(self):
        self.__exit__(None, None, None)

Voila donc la fin de cet exercice, et voila ma solution au complet:

import os
import shutil
import tempfile


class cd(object):
    def __init__(self, new_cwd=None):
        self.new_cwd = new_cwd
        self.old_cwd = None
        self.temporary_directory = None

    def __enter__(self):
        self.old_cwd = os.getcwd()
        if self.new_cwd is None:
            # create a temporary directory
            self.temporary_directory = tempfile.mkdtemp()
            try:
                shutil.rmtree(self.temporary_directory)
            except FileNotFoundError:
                pass
            os.makedirs(self.temporary_directory)
            os.chdir(self.temporary_directory)
        else:
            os.chdir(self.new_cwd)
        return self

    def __exit__(self, exc_type, exc_value, exc_traceback):
        os.chdir(self.old_cwd)
        if self.temporary_directory:
            shutil.rmtree(self.temporary_directory)

    def enter(self):
        self.__enter__()

    def leave(self):
        self.__exit__(None, None, None)