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