Передать аргумент __enter__

Просто учусь с заявлениями, особенно из этой статьи

Вопрос в том, могу ли я передать аргумент __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__, Это возможно?

7 ответов

Решение

Нет, ты не можешь Вы передаете аргументы __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

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


    #!/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

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

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 диспетчер контекста - это любой объект, определяющий метод ввода. Этот метод вызывается автоматически, когда вы это делаете;

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

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

Возможно, вам пришла в голову идея передачи аргументов для входа;

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

Но это отличается от переопределения ввода, поскольку "open" - это не объект, а функция.

Хорошее упражнение - открыть интерактивную консоль Python и набрать "open" + [ENTER].

>>> open
<built-in function open>

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

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

..вы можете определить свои собственные функции диспетчера контекста таким же образом, вы даже можете переопределить определение "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')

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

Мне показалось, что этого ответа в партии не хватает, поэтому я добавил его.

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

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

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

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

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

Я думаю использовать 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. При желании он подавляет исключения, возникающие в блоке кода.

Вы можете сохранить состояние в экземпляре: (PS я не рекомендую это, так как это приводит к спагетти-коду)

      class Thing:

    def __init__(self):
        self.name = 'original'

    def __call__(self, name):
        self._original_name = self.name
        self.name = name
        return self

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_value, traceback):
        self.name = self._original_name

Вот тест:

      instance = Thing()
assert instance.name == 'original'
with instance('new name'):
    assert instance.name == 'new name'

assert instance.name == 'original'
Другие вопросы по тегам