Home > other >  How can I prematurely exit __enter__ and skip the with block in Python?
How can I prematurely exit __enter__ and skip the with block in Python?

Time:03-21

I've been trying to find a way to completely exit/skip my with BaseObject.ethernet_device(ip_addr) as obj: code block if there are errors in the __enter__ function.

In the example below this would be if the device is not present on the network. Any solutions would hopefully go in place of the line return None (which I know is not what's needed).

class BaseObject(object):
    @class_method
    def ethernet_device(cls, host:str):
        return EthernetDevice(host)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self._inst.close()
class EthernetDevice(BaseObject):
    def __init__(self, host: str):
        self._host = host

    def __enter__(self):
        try:
            socket.inet_ntoa(self._host)
            ip_addr = self._host
        except:
            logger.debug(f'{self._host} is not a valid IP address: trying as a hostname')
            try:
                logger.debug(f"Trying to resolve host {self._host}")
                ip_addr = socket.gethostbyname(self._host)
            except socket.gaierror:
                logger.error(f"Couldn't resolve host {self._host}")
                return None

        self._inst = vxi11.Instrument(ip_addr)

        return super().__enter__()

Usage:

with BaseObject.ethernet_device(ip_addr) as device:
   print(device.id)

CodePudding user response:

The only answer here is to raise an exception inside __enter__; __exit__ will always run if __enter__ exits without an exception (assuming you don't manage to segfault the interpreter or call os._exit to intentionally kill the program without running cleanup blocks).

This will put it on the caller to catch the exception (wrapping the with with a try/except) to prevent it bubbling through the rest of the program.

If the caller doesn't like indenting the whole block for this, they can narrow the scope using an ExitStack, but that's the only "improvement" available, and it still requires __enter__ to raise an exception:

with contextlib.ExitStack() as stack:
    try:
        device = stack.enter_context(BaseObject.ethernet_device(ip_addr)
    except CustomException:
        return  # Or do something else to skip remaining code
    # Rest of code using device

# device is cleaned up for you if it exists
  • Related