Search code examples
pythonpython-3.xcontextmanager

How to append values in list atomically (on exit) using context manager?


For starters, i'm newbee in Python.

I'm trying to write my own context manager, which'll append values to my list on exit. I created a copy of my list, but it seems this doesn't work properly.

Here's my code:

import time
import copy

a = [1,2,3]


class contextmanager():
    def __init__(self, var):
        self.abc = var

    def current(self):

        b.append(98325)



        return b

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('Current list: {}'.format(self.current()))

with contextmanager(a) as t:
    time.sleep(1)
    b = copy.deepcopy(a)

    print("Changed list: {}".format(t.current()))
    time.sleep(2) 

I want to add values on exit, like:

current - [1,2,3]

append - [4,5,6]

before exit - [1,2,3]

exit - [1,2,3,4,5,6]

but it doesn't work out that way and i got that:

current - [1,2,3,4,5,6]

append - [4,5,6]

exit - [1,2,3,4,5,6,4,5,6]

What am i doing wrong? How can i do that to fix it? I will be glad if you show me what i need to change in my code.

I don't seem to know Python that well yet.. Thanks for any of your help.


Solution

  • This is not the way you are suppose to use a context manager. First OO principles recommend to have proper encapsulation, which mean that the user should not have to know the implementation details.

    I would propose the following design:

    • the context manager take a list as its creation parameter
    • it provides 2 methods:
      • append(val) which prepares appending val to the list and returns itself to allow chaining
      • appending() which returns the list of items to append
    • the prepared items are appended only if no exception was raised

    Possible implementation:

    class contextmanager():
        def __init__(self, var):
            self.orig = var
            self.cur = []
    
        def append(self, val):
            self.cur.append(val)
            return self
    
        def appending(self):
            return self.cur
    
        def __enter__(self):
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is None:
                self.orig.extend(self.cur)
    

    Usage example:

    a = [1,2,3]
    
    print("initial", a)
    with contextmanager(a) as t:
        t.append(4)
        print("appending", t.appending())
        t.append(5).append(6)
        print("appending", t.appending())
    
    print("first pass", a)
    
    try:
        with contextmanager(a) as t:
            t.append(7)
            t.append(8)
            print("appending", t.appending())
            raise Exception()
            t.append(9)
            print("appending", t.appending())
    except Exception as e:
        print("Exception", e)
    
    print("final", a)
    

    which gives:

    initial [1, 2, 3]
    appending [4]
    appending [4, 5, 6]
    first pass [1, 2, 3, 4, 5, 6]
    appending [7, 8]
    Exception 
    final [1, 2, 3, 4, 5, 6]