将参数传递给 __enter__

发布于 2024-10-19 11:54:18 字数 581 浏览 5 评论 0原文

只是了解 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__。这可能吗?

Just learning about with statements especially from this article

question is, can I pass an argument to __enter__?

I have code like this:

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

I'd like to pass filename and param_dict as parameters to __enter__. Is that possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

追我者格杀勿论 2024-10-26 11:54:18

是的,多加一点代码就可以得到效果。


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

Yes, you can get the effect by adding a little more 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
你如我软肋 2024-10-26 11:54:18

不,你不能。您将参数传递给__init__()

class ClippyRunner:
    def __init__(self, *args):
        # save args as attributes 
        self._args = args
    
    def __enter__(self):
        # Do something with args
        print(self._args)


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

No. You can't. You pass arguments to __init__().

class ClippyRunner:
    def __init__(self, *args):
        # save args as attributes 
        self._args = args
    
    def __enter__(self):
        # Do something with args
        print(self._args)


with ClippyRunner(args) as something:
    # work with "something"
    pass
紫瑟鸿黎 2024-10-26 11:54:18

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

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
        return self  # EDIT as pointed out by @MarkLoyman

..这样,您仍然可以使用所需的任何参数初始化对象,并像平常一样对对象执行任何其他操作,但是当您使用该对象作为上下文管理器时,您首先调用它的 调用 函数并为上下文管理器设置一些参数。

这里重要的是准确理解上下文管理器在 Python 中的工作原理。

在 Python 中,上下文管理器是定义 enter 方法的任何对象。当您执行此操作时,会自动调用此方法;

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

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

您可能已经想到将参数传递给 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(..):
    ...

..你可以用同样的方式定义你自己的上下文管理器函数,你甚至可以覆盖“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.__enter__()

我觉得这个答案在很多地方都缺失了,所以我添加了它。

编辑:将“return self”添加到Comedian.__call__,如@MarkLoyman所指出的

The accepted answer (Which I feel is incorrect) states that you CAN'T, and that you should instead do;

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

..and while this is often what you would do, it is not always the best solution, or even a solution, and it is definitely not the only solution!..

I assume that what you want is to be able to do something similar to;

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

If this is the case, and it is not more complicated than that, then you can just do;

class Comedian:
    def __enter__(self):
        jokes = self.jokes
        #say some funny jokes
        return self
    def __call__(self, *jokes):
        self.jokes = jokes
        return self  # EDIT as pointed out by @MarkLoyman

..That way you can still initialize the object with any arguments that you want, and do any other stuff with the object like you would usually, but when you go to use the object as a context manager you first call its call function and set up some args for the context manager.

The important thing here is to understand exactly how context managers work in Python.

In Python a context manager is any object that defines an enter method. This method is called automatically when you do;

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

..Notice how object doesn't have a couple of "()" after it, it is an implicit function call, and it doesn't take any arguments.

You might have gotten the idea of passing arguments to enter from;

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

But this is different from overriding enter, as "open" is not an object, but a function.

A good exercise is to open an interactive python console and type "open" + [ENTER]

>>> open
<built-in function open>

"open" is not a context manager object, but function. It doesn't have an enter method at all, instead it is defined in the following way;

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

..you can define your own context manager functions in the same way, you can even override the definition of "open".

IMO though, the best thing to do if you need to create an object and then later use it as a context manager with arguments (..what I do) is to give the object a method that returns a temporary object that defines an enter method, like so;

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

The order of the call-chain in this example is;
Comedian.__init__() -> Comedian.context(args) -> Roaster.__enter__()

I felt like this answer was missing from the lot, so I added it.

EDIT: Added "return self" to Comedian.__call__ as pointed out by @MarkLoyman

东风软 2024-10-26 11:54:18

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

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

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

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

You can use the contextmanager decorator to pass arguments:

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

from contextlib import contextmanager

@contextmanager
def clippy_runner(*args):
    yield

IMHO, I find confusing that using contextmanager you can provide arguments, but you cannot provide them to __enter__

凉栀 2024-10-26 11:54:18

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

Wouldn't you just pass the values to __init__ via the class constructor?

计㈡愣 2024-10-26 11:54:18

我认为使用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. 或者,它可以抑制代码块内引发的异常。

I think to use the contextlib.contextmanager(native package) is a good idea.

More details, see as follows.

a simple example

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

output

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

example of yours

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

output

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

About contextmanager

A context manager does (basically) three things:

  1. It runs some code before a code block.
  2. It runs some code after a code block.
  3. Optionally, it suppresses exceptions raised within a code block.
娇纵 2024-10-26 11:54:18

您可以在实例中保存状态:(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'

You can save the state in the instance: (PS I don't recommend this as it leads to spaghetti code)

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

Here is the test:

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

assert instance.name == 'original'
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文