Unexpected behavior using contextmanager on a class method

Discussion in 'Python' started by Thomas Draper, Aug 7, 2012.

  1. I want to use with..as in a "reversible circuit generator". However, it seems that @contextmanager changes the expected nature of the class. I tried to distill the problem down to a simple example.

    import contextlib

    class SymList:
    def __init__(self, L=[]):
    self.L = L

    @contextlib.contextmanager
    def SymAdd(self, a):
    self.L.append(a)
    yield
    self.L.append(a)

    SL = SymList()
    with SL.SymAdd(3):
    SL.L.append(5)
    print(SL.L) # Expect and see [3, 5, 3]
    SL2 = SymList() # New object. Should have default values.
    print(SL2.L) # Expect [] and see [3, 5, 3]

    Why is the data member SL2.L referring to the data member SL.L? Has the @contextmanager somehow made all instantions of the class related?
    Thomas Draper, Aug 7, 2012
    #1
    1. Advertising

  2. Thomas Draper

    Peter Otten Guest

    Thomas Draper wrote:

    > I want to use with..as in a "reversible circuit generator". However, it
    > seems that @contextmanager changes the expected nature of the class. I
    > tried to distill the problem down to a simple example.
    >
    > import contextlib
    >
    > class SymList:


    The problem you experience has nothing to do with context managers, you have
    a mutable default argument in your __init__().

    > def __init__(self, L=[]):


    L is initialised with an empty list exactly once, when the method is
    defined; any changes you make to the list will be seen by all instances that
    use the default. The fix is

    def __init__(self, L=None):
    if L is None:
    L = []

    > self.L = L
    >
    > @contextlib.contextmanager
    > def SymAdd(self, a):
    > self.L.append(a)
    > yield
    > self.L.append(a)
    >
    > SL = SymList()
    > with SL.SymAdd(3):
    > SL.L.append(5)
    > print(SL.L) # Expect and see [3, 5, 3]
    > SL2 = SymList() # New object. Should have default values.
    > print(SL2.L) # Expect [] and see [3, 5, 3]
    >
    > Why is the data member SL2.L referring to the data member SL.L? Has the
    > @contextmanager somehow made all instantions of the class related?
    Peter Otten, Aug 7, 2012
    #2
    1. Advertising

  3. On Tue, 07 Aug 2012 08:30:15 -0700, Thomas Draper wrote:

    > I want to use with..as in a "reversible circuit generator". However, it
    > seems that @contextmanager changes the expected nature of the class. I
    > tried to distill the problem down to a simple example.


    Nothing to do with contextmanager. That's a red-herring. Your error is
    here:

    class SymList:
    def __init__(self, L=[]):
    self.L = L

    The default value for L is only set *once*, when the function is defined,
    NOT every time the function is called. Later on, in the SymAdd method you
    modify that list in place. So naturally later instances see the changes,
    because you have changed the default list.

    You can see this "early binding" of the default value in action with this
    simple example:


    import time
    def test(x=time.ctime()): # Default values are set *once*, not each time.
    print(x)

    test()
    => always prints Wed Aug 8 03:40:32 2012

    (or whatever time the function is defined).

    In this example, the default value is a string, and cannot be changed;
    but in your code it is a list, and can be modified in place. Either way,
    the result is the same: you get the same object used as the default, each
    and every time.


    In your case, you can fix this problem and get the effect of "late
    binding" like this:

    class SymList:
    def __init__(self, L=None):
    if L is None: L = []
    self.L = L


    Now each time the method body runs, you get a different empty list.



    --
    Steven
    Steven D'Aprano, Aug 7, 2012
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    7
    Views:
    290
    Arnaud Delobelle
    Nov 28, 2007
  2. Replies:
    5
    Views:
    326
    Bruno Desthuilliers
    Jan 6, 2008
  3. Ian Kelly
    Replies:
    0
    Views:
    440
    Ian Kelly
    Jun 10, 2011
  4. Replies:
    9
    Views:
    303
    Steven D'Aprano
    Feb 1, 2012
  5. Michele Simionato

    A better contextlib.contextmanager

    Michele Simionato, May 23, 2012, in forum: Python
    Replies:
    0
    Views:
    117
    Michele Simionato
    May 23, 2012
Loading...

Share This Page