How to use the context manager to avoid the use of __del__ in python?

前端 未结 3 1508
刺人心
刺人心 2020-12-31 11:17

As it is common knowledge, the python __del__ method should not be used to clean up important things, as it is not guaranteed this method gets called. The alter

相关标签:
3条回答
  • 2020-12-31 12:04

    I'm not quite sure what you're asking. A context manager instance can be a class member - you can re-use it in as many with clauses as you like and the __enter__() and __exit__() methods will be called each time.

    So, once you'd added those methods to MyWrapper, you can construct it in MyClass just as you are above. And then you'd do something like:

    def my_method(self):
        with self.mydevice:
            # Do stuff here
    

    That will call the __enter__() and __exit__() methods on the instance you created in the constructor.

    However, the with clause can only span a function - if you use the with clause in the constructor then it will call __exit__() before exiting the constructor. If you want to do that, the only way is to use __del__(), which has its own problems as you've already mentioned. You could open and close the device just when you need it using with but I don't know if this fulfills your requirements.

    0 讨论(0)
  • 2020-12-31 12:11

    I suggest using the contextlib.contextmanager class instead of writing a class that implements __enter__ and __exit__. Here's how it would work:

    class MyWrapper(object):
        def __init__(self, device):
            self.device = device
    
        def open(self):
            self.device.open()
    
        def close(self):
            self.device.close()
    
        # I assume your device has a blink command
        def blink(self):
            # do something useful with self.device
            self.device.send_command(CMD_BLINK, 100)
    
        # there is no __del__ method, as long as you conscientiously use the wrapper
    
    import contextlib
    
    @contextlib.contextmanager
    def open_device(device):
        wrapper_object = MyWrapper(device)
        wrapper_object.open()
        try:
            yield wrapper_object
        finally:
            wrapper_object.close()
        return
    
    with open_device(device) as wrapper_object:
         # do something useful with wrapper_object
         wrapper_object.blink()
    

    The line that starts with an at sign is called a decorator. It modifies the function declaration on the next line.

    When the with statement is encountered, the open_device() function will execute up to the yield statement. The value in the yield statement is returned in the variable that's the target of the optional as clause, in this case, wrapper_object. You can use that value like a normal Python object thereafter. When control exits from the block by any path – including throwing exceptions – the remaining body of the open_device function will execute.

    I'm not sure if (a) your wrapper class is adding functionality to a lower-level API, or (b) if it's only something you're including so you can have a context manager. If (b), then you can probably dispense with it entirely, since contextlib takes care of that for you. Here's what your code might look like then:

    import contextlib
    
    @contextlib.contextmanager
    def open_device(device):
        device.open()
        try:
            yield device
        finally:
            device.close()
        return
    
    with open_device(device) as device:
         # do something useful with device
         device.send_command(CMD_BLINK, 100)
    

    99% of context manager uses can be done with contextlib.contextmanager. It is an extremely useful API class (and the way it's implemented is also a creative use of lower-level Python plumbing, if you care about such things).

    0 讨论(0)
  • 2020-12-31 12:16

    The issue is not that you're using it in a class, it's that you want to leave the device in an "open-ended" way: you open it and then just leave it open. A context manager provides a way to open some resource and use it in a relatively short, contained way, making sure it is closed at the end. Your existing code is already unsafe, because if some crash occurs, you can't guarantee that your __del__ will be called, so the device may be left open.

    Without knowing exactly what the device is and how it works, it's hard to say more, but the basic idea is that, if possible, it's better to only open the device right when you need to use it, and then close it immediately afterwards. So your processing is what might need to change, to something more like:

    def processing(self, value):
         with self.device:
            if value:
                something_else()
    

    If self.device is an appropriately-written context manager, it should open the device in __enter__ and close it in __exit__. This ensures that the device will be closed at the end of the with block.

    Of course, for some sorts of resources, it's not possible to do this (e.g., because opening and closing the device loses important state, or is a slow operation). If that is your case, you are stuck with using __del__ and living with its pitfalls. The basic problem is that there is no foolproof way to leave the device "open-ended" but still guarantee it will be closed even in the event of some unusual program failure.

    0 讨论(0)
提交回复
热议问题