50 votes

passer l'argument à __enter__

Je me renseigne sur les déclarations notamment de cet article

La question est de savoir si je peux passer un argument à __enter__ ?

J'ai un code comme celui-ci :

class clippy_runner:
    def __enter__(self):
        self.engine = ExcelConnection(filename = "clippytest\Test.xlsx")
        self.db = SQLConnection(param_dict = DATASOURCES[STAGE_RELATIONAL])

        self.engine.connect()
        self.db.connect()

        return self

Je voudrais passer le nom du fichier et le dictionnaire de paramètres comme paramètres à __enter__ . Est-ce possible ?

50voto

Jonathan D. Lettvin Points 396

Oui, vous pouvez obtenir cet effet en ajoutant un peu plus de code.

    #!/usr/bin/env python

    class Clippy_Runner( dict ):
        def __init__( self ):
            pass
        def __call__( self, **kwargs ):
            self.update( kwargs )
            return self
        def __enter__( self ):
            return self
        def __exit__( self, exc_type, exc_val, exc_tb ):
            self.clear()

    clippy_runner = Clippy_Runner()

    print clippy_runner.get('verbose')     # Outputs None
    with clippy_runner(verbose=True):
        print clippy_runner.get('verbose') # Outputs True
    print clippy_runner.get('verbose')     # Outputs None

7 votes

Cela me semble être la bonne réponse, car vous n'avez pas besoin de créer la variable dans l'instruction with, mais vous pouvez utiliser un objet déjà créé (comme un verrou) et passer les variables à l'instruction with. Excellente réponse !

2 votes

Excellente réponse ! Cette réponse devrait être acceptée, car elle peut être utilisée dans une for par exemple, sans instancier un nouvel objet à chaque itération.

49voto

S.Lott Points 207588

Non. Vous ne pouvez pas. Vous passez des arguments à __init__() .

class ClippyRunner:
    def __init__(self, *args):
       self._args = args

    def __enter__(self):
       # Do something with args
       print(self._args)

with ClippyRunner(args) as something:
    # work with "something"
    pass

7 votes

Je suis confus. Parce que vous venez juste pass sur __init__ Suggérez-vous que args transmis à __init__ sont disponibles dans le __enter__ fonction ?

1 votes

Hovis : les args passés à init peuvent être sauvegardés et ensuite utilisés dans la méthode enter. def __init__(self, filename, param_dict): self.filename=filename self.param_dict=param_dict def __enter__(self): self.filename ...

1 votes

Pourquoi est-ce la réponse acceptée ? Je ne semble pas être le seul à penser que c'est la pire réponse de toutes. "Non vous ne pouvez pas, vous passez des arguments à init " n'est tout simplement pas vrai, bien sûr, passer des arguments à travers init est une méthode pour le faire, mais ce n'est en aucun cas la seule façon, et ce n'est pas toujours la façon qui a le plus de sens Si vous voulez passer les arguments au gestionnaire de contexte à la ligne with as comme le voulait le PO, alors vous pouvez certainement le faire et c'est souvent ce qui a le plus de sens, selon l'application. (Voir toutes les autres réponses)

14voto

Vinzent Points 121

La réponse acceptée (qui me semble incorrecte) indique que vous ne pouvez pas, et que vous devriez plutôt faire ;

class Comedian:
    def __init__(self, *jokes):
        self.jokes = jokes
    def __enter__(self):
        jokes = self.jokes
        #say some funny jokes
        return self

et bien que ce soit souvent ce que vous feriez, ce n'est pas toujours la meilleure solution, ou même a solution, et c'est définitivement pas la seule solution ! ..

Je suppose que ce que vous voulez, c'est être capable de faire quelque chose de similaire à ;

funny_object = Comedian()
with funny_object('this is a joke') as humor:
    humor.say_something_funny()

Si c'est le cas, et que ce n'est pas plus compliqué que cela, alors vous pouvez tout simplement le faire ;

class Comedian:
    def __enter__(self):
        jokes = self.jokes
        #say some funny jokes
        return self
    def __call__(self, *jokes):
        self.jokes = jokes

De cette façon, vous pouvez toujours initialiser l'objet avec tous les arguments que vous voulez, et faire n'importe quelle autre chose avec l'objet comme vous le feriez habituellement, mais lorsque vous voulez utiliser l'objet comme un gestionnaire de contexte, vous appelez d'abord sa fonction appelez et configurer quelques arguments pour le gestionnaire de contexte.

L'important ici est de comprendre exactement comment les gestionnaires de contexte fonctionnent en Python.

En Python, un gestionnaire de contexte est tout objet qui définit un entrez méthode. Cette méthode est appelée automatiquement lorsque vous le faites ;

with object as alias:
    alias.do_stuff()
    ..

Remarquez que l'objet n'est pas suivi de deux "()", qu'il s'agit d'un appel de fonction implicite et qu'il ne prend aucun argument.

Vous avez peut-être eu l'idée de passer des arguments à entrez de ;

with open(filename) as file:
    "do stuff with file..

Mais ceci est différent de la substitution de entrez car "ouvert" n'est pas un objet, mais une fonction.

Un bon exercice consiste à ouvrir une console python interactive et à taper "open" + [ENTER].

>>> open
<built-in function open>

"open" n'est pas un gestionnaire de contexte objet mais fonction . Il n'a pas de entrez mais elle est définie de la manière suivante ;

@contextmanager
def open(..):
    ...

vous pouvez définir vos propres fonctions de gestionnaire de contexte de la même manière, vous pouvez même remplacer la définition de "open".

IMO cependant, la meilleure chose à faire si vous devez créer un objet et l'utiliser plus tard comme un gestionnaire de contexte avec des arguments ( ce que je fais) est de donner à l'objet une méthode qui retourne un objet temporaire qui définit un entrez comme suit ;

class Comedian:
    def context(audience):
        class Roaster:
            context = audience
            def __enter__(self):
                audience = self.__class__.context
                # a comedian needs to know his/her audience.
        return Roaster(audience)

funny_thing = Comedian()
with funny_thing.context('young people') as roaster:
    roaster.roast('old people')

L'ordre de la chaîne d'appel dans cet exemple est le suivant ; Comédien. init () -> Comedian.context(args) -> Roaster. entrez ()

J'avais l'impression que cette réponse manquait dans le lot, alors je l'ai ajoutée.

7voto

José Luis Points 592

Vous pouvez utiliser le décorateur contextmanager pour passer des arguments :

https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

IMHO, je trouve déroutant que l'utilisation de contextmanager vous pouvez fournir des arguments, mais vous ne pouvez pas les fournir pour __enter__

3 votes

Je suis d'accord avec ça. J'ai des paramètres qui ne sont pertinents que dans le contexte. En les passant à __init__ est stupide.

3voto

senderle Points 41607

Ne pourriez-vous pas simplement passer les valeurs à __init__ via le constructeur de la classe ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X