Передать аргумент __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
Менеджер контекста выполняет (в основном) три вещи:
- Он запускает некоторый код перед блоком кода.
- Он запускает некоторый код после блока кода.
- При желании он подавляет исключения, возникающие в блоке кода.
Вы можете сохранить состояние в экземпляре: (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'