передать аргумент в __enter__

Просто узнайте об утверждениях with особенно из этой статьи

вопрос в том, могу ли я передать аргумент __enter__?

У меня есть такой код:

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

Я хотел бы передать имя файла и param_dict в качестве параметров для __enter__. Это возможно?


person Ramy    schedule 24.02.2011    source источник


Ответы (6)


Да, вы можете получить эффект, добавив еще немного кода.


    #!/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
person Jonathan D. Lettvin    schedule 20.04.2012
comment
Это кажется правильным ответом на мой вопрос, так как вам не нужно создавать переменную в операторе with, но вы можете использовать готовый объект (например, блокировку) и передавать переменные оператору with. Отличный ответ! - person rubmz; 25.08.2018
comment
Отличный ответ! Это должно быть принято, поскольку его можно использовать, например, в цикле for, без создания экземпляра нового объекта на каждой итерации. - person Maor Refaeli; 30.08.2018

Нет. Вы не можете. Вы передаете аргументы __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
person S.Lott    schedule 24.02.2011
comment
Я в замешательстве. Поскольку вы просто pass в __init__, вы предполагаете, что args, переданные в __init__, доступны в функции __enter__? - person Hovis Biddle; 29.08.2013
comment
Hovis: аргументы, переданные в init, можно сохранить, а затем использовать в методе ввода. def __init__(self, filename, param_dict): self.filename=filename self.param_dict=param_dict def __enter__(self): self.filename ... - person spazm; 21.11.2014
comment
Почему это принятый ответ? Кажется, я не единственный, кто думает, что это худший ответ из всех. Нет, вы не можете, вы передаете аргументы в init.. это просто неверно, передача аргументов через init — это один из способов сделать это, но это ни в коем случае не единственный способ, а также не всегда так, как это имеет смысл. Если вы хотите передать аргументы диспетчеру контекста в строке with .. as .. , как хотел OP, вы определенно можете, и это часто имеет смысл, в зависимости от заявление. (см. все остальные ответы) - person Vinzent; 20.05.2020

В принятом ответе (который я считаю неверным) говорится, что вы НЕ МОЖЕТЕ, и вместо этого вы должны это сделать;

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

..и хотя это часто то, что вы бы сделали, это не всегда лучшее решение или даже решение, и это определенно не единственное решение!..

Я предполагаю, что вы хотите иметь возможность сделать что-то похожее на;

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

Если это так, и это не сложнее, то вы можете просто сделать;

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

.. Таким образом, вы по-прежнему можете инициализировать объект с любыми аргументами, которые хотите, и делать с объектом любые другие действия, как обычно, но когда вы переходите к использованию объекта в качестве менеджера контекста, вы сначала вызываете его вызов и настройте некоторые аргументы для менеджера контекста.

Здесь важно точно понимать, как контекстные менеджеры работают в Python.

В Python менеджер контекста — это любой объект, определяющий метод enter. Этот метод вызывается автоматически, когда вы это делаете;

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

..Обратите внимание, что у объекта нет пары "()" после него, это неявный вызов функции, и он не принимает никаких аргументов.

Возможно, вам пришла в голову идея передавать аргументы для enter from;

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

Но это отличается от переопределения enter, поскольку «open» — это не объект, а функция.

Хорошее упражнение — открыть интерактивную консоль Python и ввести «open» + [ENTER]

>>> open
<built-in function open>

"open" - это не объект диспетчера контекста, а функция. У него вообще нет метода enter, вместо этого он определяется следующим образом;

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

.. вы можете определить свои собственные функции контекстного менеджера таким же образом, вы даже можете переопределить определение «открыть».

ИМО, однако, лучше всего, если вам нужно создать объект, а затем использовать его в качестве диспетчера контекста с аргументами (... что я делаю), это дать объекту метод, который возвращает временный объект, который определяет введите метод, например;

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')

Порядок цепочки вызовов в этом примере: Comedian.init() -> Comedian.context(args) -> Roaster.enter()

Я чувствовал, что этот ответ отсутствует в лоте, поэтому я добавил его.

person Vinzent    schedule 20.05.2020

Вы можете использовать декоратор contextmanager для передачи аргументов:

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

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

ИМХО, меня сбивает с толку то, что с помощью contextmanager вы можете предоставлять аргументы, но вы не можете предоставлять их __enter__

person José Luis    schedule 16.12.2016
comment
Я поддерживаю это. У меня есть некоторые настройки, которые актуальны только внутри контекста. Передавать их __init__ глупо. - person Muposat; 08.05.2017

Не могли бы вы просто передать значения __init__ через конструктор класса?

person senderle    schedule 24.02.2011

Я думаю, что использовать contextlib.contextmanager (родной пакет) — хорошая идея.

Подробнее см. ниже.

простой пример

from contextlib import contextmanager


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

    def say_something(self, msg):
        print(f'{self.name}: {msg}')

    @staticmethod
    @contextmanager
    def enter(name,  # <-- members of construct
              para_1, options: dict  # <-- Other parameter that you wanted.
              ):
        with Person(name) as instance_person:
            try:
                print(para_1)
                print(options)
                yield instance_person
            finally:
                ...

    def __enter__(self):
        print(self.name)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')


with Person.enter('Carson', para_1=1, options=dict(key='item_1')) as carson:
    carson.say_something('age=28')
    print('inside')
print('outside')

выход

Carson
1
{'key': 'item_1'}
Carson: age=28
inside
__exit__
outside

пример твой

from typing import Union
from contextlib import contextmanager


def main():
    with ClippyRunner.enter(filename="clippytest/Test.xlsx",
                            param_dict='DATASOURCES[STAGE_RELATIONAL]') as clippy_runner:
        clippy_runner.do_something()


class ConnectBase:
    def connect(self):
        print(f'{type(self).__name__} connect')

    def disconnect(self):
        print(f'{type(self).__name__} disconnect')


class ExcelConnection(ConnectBase):
    def __init__(self, filename):
        self.filename = filename


class SQLConnection(ConnectBase):
    def __init__(self, param_dict):
        self.param_dict = param_dict


class ClippyRunner:
    def __init__(self, engine: Union[ExcelConnection], db: Union[SQLConnection]):
        self.engine = engine
        self.db = db

    def do_something(self):
        print('do something...')

    @staticmethod
    @contextmanager
    def enter(filename, param_dict):
        with ClippyRunner(ExcelConnection(filename),
                          SQLConnection(param_dict)) as cr:
            try:
                cr.engine.connect()
                cr.db.connect()
                yield cr
            except:
                cr.release()  # disconnect
            finally:
                ...

    def __enter__(self):
        return self

    def release(self):
        self.engine.disconnect()
        self.db.disconnect()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.release()


if __name__ == '__main__':
    main()

выход

ExcelConnection connect
SQLConnection connect
do something...
ExcelConnection disconnect
SQLConnection disconnect

О contextmanager

Контекстный менеджер делает (в основном) три вещи:

  1. Он запускает некоторый код перед блоком кода.
  2. Он запускает некоторый код после блока кода.
  3. При необходимости он подавляет исключения, возникающие в блоке кода.
person Carson    schedule 29.04.2020