How does DCPenChanger work?

I just learned about DCPenChanger. It sounds like a nice tool. But
then I saw its `__exit__` method:

    def __exit__(self, exc_type, exc_val, exc_tb):
                return False

How does it work? I thought that the idea was to change the pen back
on exit, so how does that happen?

Ram.

The __enter__ and __exit__ methods are just to make the object be a context manager so it will work with the 'with' statement. The real magic happens in the C++ constructor and destructor.

···

On 9/20/10 6:08 AM, cool-RR wrote:

I just learned about DCPenChanger. It sounds like a nice tool. But
then I saw its `__exit__` method:

     def __exit__(self, exc_type, exc_val, exc_tb):
                 return False

How does it work? I thought that the idea was to change the pen back
on exit, so how does that happen?

--
Robin Dunn
Software Craftsman

But who calls the C++ destructor?

Ram.

···

On Sep 20, 8:11 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 9/20/10 6:08 AM, cool-RR wrote:

> I just learned about DCPenChanger. It sounds like a nice tool. But
> then I saw its `__exit__` method:

> def __exit__(self, exc_type, exc_val, exc_tb):
> return False

> How does it work? I thought that the idea was to change the pen back
> on exit, so how does that happen?

The __enter__ and __exit__ methods are just to make the object be a
context manager so it will work with the 'with' statement. The real
magic happens in the C++ constructor and destructor.
--
Robin Dunn

It is called automatically just before the object is destroyed.

···

On 9/20/10 11:14 AM, cool-RR wrote:

On Sep 20, 8:11 pm, Robin Dunn<ro...@alldunn.com> wrote:

On 9/20/10 6:08 AM, cool-RR wrote:

I just learned about DCPenChanger. It sounds like a nice tool. But
then I saw its `__exit__` method:

      def __exit__(self, exc_type, exc_val, exc_tb):
                  return False

How does it work? I thought that the idea was to change the pen back
on exit, so how does that happen?

The __enter__ and __exit__ methods are just to make the object be a
context manager so it will work with the 'with' statement. The real
magic happens in the C++ constructor and destructor.
--
Robin Dunn

But who calls the C++ destructor?

--
Robin Dunn
Software Craftsman

What do you mean "destroyed"? Having its `.Destroy()` method called?
Or garbage-collected?

···

On Sep 20, 8:19 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 9/20/10 11:14 AM, cool-RR wrote:
> On Sep 20, 8:11 pm, Robin Dunn<ro...@alldunn.com> wrote:
>> On 9/20/10 6:08 AM, cool-RR wrote:

>>> I just learned about DCPenChanger. It sounds like a nice tool. But
>>> then I saw its `__exit__` method:

>>> def __exit__(self, exc_type, exc_val, exc_tb):
>>> return False

>>> How does it work? I thought that the idea was to change the pen back
>>> on exit, so how does that happen?

>> The __enter__ and __exit__ methods are just to make the object be a
>> context manager so it will work with the 'with' statement. The real
>> magic happens in the C++ constructor and destructor.
>> --
>> Robin Dunn

> But who calls the C++ destructor?

It is called automatically just before the object is destroyed.
Robin Dunn

There is no garbage collection in C++, and usually Python garbage collection will not come into play here either since simple reference counting will take care of this case. When the reference count of the Python object goes to zero then Python will deallocate the PyObject, and as part of that a hook function is called in the swigged wrapper code that will do a C++ "delete theObject" which will cause the C++ destructor to be called.

···

On 9/20/10 11:22 AM, cool-RR wrote:

On Sep 20, 8:19 pm, Robin Dunn<ro...@alldunn.com> wrote:

On 9/20/10 11:14 AM, cool-RR wrote:

On Sep 20, 8:11 pm, Robin Dunn<ro...@alldunn.com> wrote:

On 9/20/10 6:08 AM, cool-RR wrote:

I just learned about DCPenChanger. It sounds like a nice tool. But
then I saw its `__exit__` method:

       def __exit__(self, exc_type, exc_val, exc_tb):
                   return False

How does it work? I thought that the idea was to change the pen back
on exit, so how does that happen?

The __enter__ and __exit__ methods are just to make the object be a
context manager so it will work with the 'with' statement. The real
magic happens in the C++ constructor and destructor.
--
Robin Dunn

But who calls the C++ destructor?

It is called automatically just before the object is destroyed.
Robin Dunn

What do you mean "destroyed"? Having its `.Destroy()` method called?
Or garbage-collected?

--
Robin Dunn
Software Craftsman

A reasonable guess is that it first queries the pen type, saves that
value,
sets a new pen and then restores it when the function returns.

I think you have asked the wrong question.

__exit__() has nothing to do with saving/restoring the pen value.
That is done in by the wxWidgets C++ function.

This is a SWIG'ed compiled C++ object code function.
All that can be seen is the wxPython wrapper code.

How would *you* design a class using Python to restore a value
when a function it is exited ? Whatever that is, that's probably how
it's
done using the equivalent C++ code.

Ray

···

On Sep 20, 9:08 am, cool-RR <ram.rac...@gmail.com> wrote:

I just learned about DCPenChanger. It sounds like a nice tool. But
then I saw its `__exit__` method:

def \_\_exit\_\_\(self, exc\_type, exc\_val, exc\_tb\):
            return False

How does it work? I thought that the idea was to change the pen back
on exit, so how does that happen?

Ram.

Here is the C++ class for the curious:

class WXDLLIMPEXP_CORE wxDCPenChanger
{
public:
     wxDCPenChanger(wxDC& dc, const wxPen& pen) : m_dc(dc), m_penOld(dc.GetPen())
     {
         m_dc.SetPen(pen);
     }

     ~wxDCPenChanger()
     {
         if ( m_penOld.Ok() )
             m_dc.SetPen(m_penOld);
     }

private:
     wxDC& m_dc;

     wxPen m_penOld;

     wxDECLARE_NO_COPY_CLASS(wxDCPenChanger);
};

···

On 9/20/10 11:42 AM, Ray Pasco wrote:

How would *you* design a class using Python to restore a value
when a function it is exited ? Whatever that is, that's probably how
it's
done using the equivalent C++ code.

--
Robin Dunn
Software Craftsman

I played around with it a bit now. I see that it works in a curious
way. See this example:

···

On Sep 20, 8:45 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 9/20/10 11:42 AM, Ray Pasco wrote:

> How would *you* design a class using Python to restore a value
> when a function it is exited ? Whatever that is, that's probably how
> it's
> done using the equivalent C++ code.

Here is the C++ class for the curious:

class WXDLLIMPEXP_CORE wxDCPenChanger
{
public:
wxDCPenChanger(wxDC& dc, const wxPen& pen) : m_dc(dc),
m_penOld(dc.GetPen())
{
m_dc.SetPen(pen);
}

 \~wxDCPenChanger\(\)
 \{
     if \( m\_penOld\.Ok\(\) \)
         m\_dc\.SetPen\(m\_penOld\);
 \}

private:
wxDC& m_dc;

 wxPen m\_penOld;

 wxDECLARE\_NO\_COPY\_CLASS\(wxDCPenChanger\);

};

--
Robin Dunn

#########################################

import wx

class Panel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.Bind(wx.EVT_PAINT, self.on_paint)

    def on_paint(self, event):
        dc = wx.PaintDC(self)
        pen1 = wx.Pen(wx.Color(0, 0, 0), 3, wx.SOLID)
        pen2 = wx.Pen(wx.Color(150, 0, 0), 6, wx.LONG_DASH)
        dc.SetPen(pen1)
        dc.DrawRectangle(10, 10, 100, 100)
        # with wx.DCPenChanger(dc, pen2):
        with wx.DCPenChanger(dc, pen2) as dc_pen_changer:
            dc.DrawCircle(150, 150, 40)
        dc.DrawRectangle(210, 210, 100, 100)
        dc.Destroy()

class Frame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, size=(400, 400))
        self.panel = Panel(self)

app = wx.App()
frame = Frame(None)
frame.Show()
app.MainLoop()

#########################################

In this example the pen will *not* be changed back, and then the
program crashes. All because of the `as dc_pen_changer:` in the `with`
statement. (You see that above the long `with` statement I put the
`with` statement without the `as` part, which does work.)

I understand that no one would have a reason to use `as
dc_pen_changer` because you don't really need the pen changer as an
object. Still it's a bit strange, I don't understand why the destroy
function is not called on `__exit__`. I'm getting the impression that
`DCPenChanger` is not exactly using the context manager API in the way
it was supposed to be used. But maybe you have concerns of backward
compatibility, and I understand that.

Thanks for your explanations.

Ram.

Take a look at how it would be used without a context manager statement, which is essentially how it would be used in C++:

  pc = wx.DCPenChanger(newPen)
  doSomething()
  del pc

When the del statement has run then there are no more references to the object and so it is destroyed and the C++ destructor is called as described before.

In this code:

  with wx.DCPenChanger(newPen):
    doSomething()

essentially the same thing is happening. An object is created and at the end of the block the last reference to it (the only one) goes away and so the object is cleaned up and the C++ destructor is called.

However in your sample:

  with wx.DCPenChanger(newPen) as pc:
    doSomething()

Then there are now two references to the object, one called pc and one unnamed reference that belongs to the with block. When the with block is over there is still one reference remaining (named pc) and so the C++ destructor is not called until the end of the function when all objects with local names are decref'ed. The crash is probably happening because dc is decref'ed before pc is.

So yes, wx.DCPenChanger (and others like it) are not a perfect match for Python's context manager feature, but then it was never intended to be since C++ doesn't have anything like it. (Or rather, it does but the way it is expressed is by creating an object on the stack and letting it clean up after itself at the end of the block.) But the behavior is close enough to context managers that it makes sense to add dummy __enter__ and __exit__ methods so it can be used that way if you want.

A more correct context manager implementation could be done something like this:

class DCPenChangerContextManager(object):
     def __init__(self, dc, pen):
         self.dc = dc
  self.pen = pen
     def __enter__(self):
         self.changer = wx.DCPenChanger(self.dc, self.pen)
         return self
     def __exit__(self, type, value, tb):
         del self.changer

But that seems like overkill to me.

···

On 9/20/10 2:59 PM, cool-RR wrote:

I played around with it a bit now. I see that it works in a curious
way. See this example:

#########################################

import wx

class Panel(wx.Panel):
     def __init__(self, parent):
         wx.Panel.__init__(self, parent)
         self.Bind(wx.EVT_PAINT, self.on_paint)

     def on_paint(self, event):
         dc = wx.PaintDC(self)
         pen1 = wx.Pen(wx.Color(0, 0, 0), 3, wx.SOLID)
         pen2 = wx.Pen(wx.Color(150, 0, 0), 6, wx.LONG_DASH)
         dc.SetPen(pen1)
         dc.DrawRectangle(10, 10, 100, 100)
         # with wx.DCPenChanger(dc, pen2):
         with wx.DCPenChanger(dc, pen2) as dc_pen_changer:
             dc.DrawCircle(150, 150, 40)
         dc.DrawRectangle(210, 210, 100, 100)
         dc.Destroy()

class Frame(wx.Frame):
     def __init__(self, parent):
         wx.Frame.__init__(self, parent, size=(400, 400))
         self.panel = Panel(self)

app = wx.App()
frame = Frame(None)
frame.Show()
app.MainLoop()

#########################################

In this example the pen will *not* be changed back, and then the
program crashes. All because of the `as dc_pen_changer:` in the `with`
statement. (You see that above the long `with` statement I put the
`with` statement without the `as` part, which does work.)

--
Robin Dunn
Software Craftsman

I'm confused. What is your conclusion ?:

1) A bug or an obvious deficiency exists.

OR

2) It is not being called properly.

And, therefore:

A) It needs to be called correctly.

OR

B) The bug and/or deficiency needs to be corrected.

OR

C) A new class needs to be written to do what is wanted.

Ray

···

On Sep 20, 7:07 pm, Robin Dunn <ro...@alldunn.com> wrote:

On 9/20/10 2:59 PM, cool-RR wrote:

> I played around with it a bit now. I see that it works in a curious
> way. See this example:

> #########################################

> import wx ...

I'd call it a square-peg-round-hole issue. The constructor/destructor model used by the C++ class is almost the same as the context manager model in Python, and with just the addition of simple dummy __enter__/__exit__ methods the peg can be made to mostly fit in the hole, with some minor gaps. As long as you don't create extra references to the changer object then it works exactly as expected.

If you want a perfectly round peg to go into the round hole then it's possible to make your own context manager like I showed in my last message. But I don't think it is worth adding yet another class to the wx namespace and the (admittedly small) maintenance burden that goes along with it when the mostly round peg works just fine if you use it in the most logical (and intended) manner.

···

On 9/21/10 10:24 AM, Ray Pasco wrote:

On Sep 20, 7:07 pm, Robin Dunn<ro...@alldunn.com> wrote:

On 9/20/10 2:59 PM, cool-RR wrote:

I played around with it a bit now. I see that it works in a curious
way. See this example:

#########################################

import wx ...

I'm confused. What is your conclusion ?:

--
Robin Dunn
Software Craftsman

Note that now that people try to use PyPy with wxPython, this approach of using __del__ instead of __exit__ will not work on PyPy, because there is no reference counting there.

Ram.

When the .this attribute of a swigged class is deallocated then the C SwigObject code calls the destroy function wrapper associated with the wrapped C++ class which then uses the C++ delete operator. If you look in the swig generated .py files you'll see that the __del__ methods are all defined like this:

     __del__ = lambda self : None;

BTW, __exit__ is for context managers (used with the 'with' statement).

···

On 5/24/11 1:41 AM, cool-RR wrote:

Note that now that people try to use PyPy with wxPython, this approach
of using `__del__` instead of `__exit__` will not work on PyPy, because
there is no reference counting there.

--
Robin Dunn
Software Craftsman

Sorry, I didn't realize that this was a continuation from the conversation last fall until I saw how this reply was attached to that thread. So to put it in better context with the rest of the former conversation: The swigged classes do not really use the __del__ method defined in the Python class at all, but rather hook in to the deallocation of the .this attribute. So assuming that PyPy will deallocate the object shortly after the variable goes out of scope or is deleted then things should still work fine. If it doesn't then I believe that there will be a lot of things besides wx objects that will break in that model.

···

On 5/24/11 12:04 PM, Robin Dunn wrote:

On 5/24/11 1:41 AM, cool-RR wrote:

Note that now that people try to use PyPy with wxPython, this approach
of using `__del__` instead of `__exit__` will not work on PyPy, because
there is no reference counting there.

When the .this attribute of a swigged class is deallocated then the C
SwigObject code calls the destroy function wrapper associated with the
wrapped C++ class which then uses the C++ delete operator. If you look
in the swig generated .py files you'll see that the __del__ methods are
all defined like this:

__del__ = lambda self : None;

BTW, __exit__ is for context managers (used with the 'with' statement).

--
Robin Dunn
Software Craftsman

Your latter scenario is true: You can’t count on PyPy to deallocate an object after it’s gone out of scope, so I guess a lot of things in wxPython would break. Do you care about PyPy compatibility? If so I’d suggest to start keeping these things in mind and not relying on deallocation. (I would consider this a better convention regardless of PyPy.)

Ram.

···

On Tue, May 24, 2011 at 9:19 PM, Robin Dunn robin@alldunn.com wrote:

On 5/24/11 12:04 PM, Robin Dunn wrote:

On 5/24/11 1:41 AM, cool-RR wrote:

Note that now that people try to use PyPy with wxPython, this approach

of using __del__ instead of __exit__ will not work on PyPy, because

there is no reference counting there.

When the .this attribute of a swigged class is deallocated then the C

SwigObject code calls the destroy function wrapper associated with the

wrapped C++ class which then uses the C++ delete operator. If you look

in the swig generated .py files you’ll see that the del methods are

all defined like this:

del = lambda self : None;

BTW, exit is for context managers (used with the ‘with’ statement).

Sorry, I didn’t realize that this was a continuation from the conversation last fall until I saw how this reply was attached to that thread. So to put it in better context with the rest of the former conversation: The swigged classes do not really use the del method defined in the Python class at all, but rather hook in to the deallocation of the .this attribute. So assuming that PyPy will deallocate the object shortly after the variable goes out of scope or is deleted then things should still work fine. If it doesn’t then I believe that there will be a lot of things besides wx objects that will break in that model.

Robin Dunn