47

刚从这篇文章中学习 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__. 那可能吗?

4

6 回答 6

48

不,你不能。您将参数传递给__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
于 2011-02-24T19:41:03.033 回答
46

是的,您可以通过添加更多代码来获得效果。


    #!/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
于 2012-04-20T20:12:03.317 回答
13

接受的答案(我认为这是不正确的)表明您不能,而是应该这样做;

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()
    ..

..注意 object 后面没有几个“()”,它是一个隐式函数调用,它不带任何参数。

您可能已经想到传递参数以从中输入

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

但这与覆盖enter不同,因为“open”不是一个对象,而是一个函数。

一个很好的练习是打开一个交互式 python 控制台并输入“open”+ [ENTER]

>>> open
<built-in function open>

“open”不是上下文管理器对象,而是函数。它根本没有enter方法,而是通过以下方式定义的;

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

..你可以用同样的方式定义你自己的上下文管理器函数,你甚至可以覆盖“open”的定义。

不过,IMO,如果您需要创建一个对象,然后将其用作带有参数的上下文管理器(..我所做的),最好的做法是为该对象提供一个方法,该方法返回一个定义输入方法的临时对象, 像这样;

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. 输入()

我觉得很多人都缺少这个答案,所以我添加了它。

于 2020-05-20T18:03:42.907 回答
7

您可以使用 contextmanager 装饰器来传递参数:

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

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

恕我直言,我发现使用contextmanager你可以提供参数,但你不能将它们提供给__enter__

于 2016-12-16T16:34:31.463 回答
3

您不只是通过__init__类构造函数将值传递给吗?

于 2011-02-24T19:42:01.427 回答
1

我认为使用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. 可选地,它抑制在代码块中引发的异常。
于 2020-04-29T07:54:24.147 回答