Skip to content

Commit bac9962

Browse files
committed
Simplify FigureCanvas multiple inheritance init by swapping bases order.
Instead of having e.g. FigureCanvasQT inherit from QWidget and then FigureCanvasBase, which requires workarounds to handle the fact that PySide.QWidget does not participate in cooperative multiple inheritance (we work around that with _allow_super_init in qt, and manual super calls in other toolkits), just swap the order of bases to (FigureCanvasBase, QWidget) and make sure that FigureCanvasBase *does* participate in cooperative multiple inheritance. Also note the changes in FigureCanvasGTK{3,4}Agg.draw, where the new inheritance order also makes more sense.
1 parent 3eadeac commit bac9962

13 files changed

+61
-87
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,7 @@ def __init__(self, figure=None):
16331633
# We don't want to scale up the figure DPI more than once.
16341634
figure._original_dpi = figure.dpi
16351635
self._device_pixel_ratio = 1
1636+
super().__init__() # Typically the GUI widget init (if any).
16361637

16371638
callbacks = property(lambda self: self.figure._canvas_callbacks)
16381639
button_pick_id = property(lambda self: self.figure._button_pick_id)
@@ -1703,14 +1704,20 @@ def pick(self, mouseevent):
17031704
def blit(self, bbox=None):
17041705
"""Blit the canvas in bbox (default entire canvas)."""
17051706

1706-
@_api.deprecated("3.6", alternative="FigureManagerBase.resize")
17071707
def resize(self, w, h):
17081708
"""
17091709
UNUSED: Set the canvas size in pixels.
17101710
17111711
Certain backends may implement a similar method internally, but this is
17121712
not a requirement of, nor is it used by, Matplotlib itself.
17131713
"""
1714+
# The entire method is actually deprecated, but we allow pass-through
1715+
# to a parent class to support e.g. QWidget.resize.
1716+
if hasattr(super(), "resize"):
1717+
return super().resize(w, h)
1718+
else:
1719+
_api.warn_deprecated("3.6", name="resize", obj_type="method",
1720+
alternative="FigureManagerBase.resize")
17141721

17151722
def draw_event(self, renderer):
17161723
"""Pass a `DrawEvent` to all functions connected to ``draw_event``."""

lib/matplotlib/backends/backend_gtk3.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def _mpl_to_gtk_cursor(mpl_cursor):
6868
_backend_gtk.mpl_to_gtk_cursor_name(mpl_cursor))
6969

7070

71-
class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
71+
class FigureCanvasGTK3(FigureCanvasBase, Gtk.DrawingArea):
7272
required_interactive_framework = "gtk3"
7373
_timer_cls = TimerGTK3
7474
manager_class = _api.classproperty(lambda cls: FigureManagerGTK3)
@@ -85,8 +85,7 @@ class FigureCanvasGTK3(Gtk.DrawingArea, FigureCanvasBase):
8585
| Gdk.EventMask.SCROLL_MASK)
8686

8787
def __init__(self, figure=None):
88-
FigureCanvasBase.__init__(self, figure)
89-
GObject.GObject.__init__(self)
88+
super().__init__(figure=figure)
9089

9190
self._idle_draw_id = 0
9291
self._rubberband_rect = None

lib/matplotlib/backends/backend_gtk3agg.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
import cairo # Presence of cairo is already checked by _backend_gtk.
88

99

10-
class FigureCanvasGTK3Agg(backend_gtk3.FigureCanvasGTK3,
11-
backend_agg.FigureCanvasAgg):
10+
class FigureCanvasGTK3Agg(backend_agg.FigureCanvasAgg,
11+
backend_gtk3.FigureCanvasGTK3):
1212
def __init__(self, figure):
13-
backend_gtk3.FigureCanvasGTK3.__init__(self, figure)
13+
super().__init__(figure=figure)
1414
self._bbox_queue = []
1515

1616
def on_draw_event(self, widget, ctx):
@@ -63,12 +63,6 @@ def blit(self, bbox=None):
6363
self._bbox_queue.append(bbox)
6464
self.queue_draw_area(x, y, width, height)
6565

66-
def draw(self):
67-
# Call these explicitly because GTK's draw is a GObject method which
68-
# isn't cooperative with Python class methods.
69-
backend_agg.FigureCanvasAgg.draw(self)
70-
backend_gtk3.FigureCanvasGTK3.draw(self)
71-
7266

7367
@_api.deprecated("3.6", alternative="backend_gtk3.FigureManagerGTK3")
7468
class FigureManagerGTK3Agg(backend_gtk3.FigureManagerGTK3):

lib/matplotlib/backends/backend_gtk3cairo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ def set_context(self, ctx):
1111
self.gc.ctx = backend_cairo._to_context(ctx)
1212

1313

14-
class FigureCanvasGTK3Cairo(backend_gtk3.FigureCanvasGTK3,
15-
backend_cairo.FigureCanvasCairo):
14+
class FigureCanvasGTK3Cairo(backend_cairo.FigureCanvasCairo,
15+
backend_gtk3.FigureCanvasGTK3):
1616

1717
def on_draw_event(self, widget, ctx):
1818
with (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar

lib/matplotlib/backends/backend_gtk4.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@
2929
from ._backend_gtk import backend_version # noqa: F401 # pylint: disable=W0611
3030

3131

32-
class FigureCanvasGTK4(Gtk.DrawingArea, FigureCanvasBase):
32+
class FigureCanvasGTK4(FigureCanvasBase, Gtk.DrawingArea):
3333
required_interactive_framework = "gtk4"
3434
supports_blit = False
3535
_timer_cls = TimerGTK4
3636
manager_class = _api.classproperty(lambda cls: FigureManagerGTK4)
3737
_context_is_scaled = False
3838

3939
def __init__(self, figure=None):
40-
FigureCanvasBase.__init__(self, figure)
41-
GObject.GObject.__init__(self)
40+
super().__init__(figure=figure)
41+
4242
self.set_hexpand(True)
4343
self.set_vexpand(True)
4444

lib/matplotlib/backends/backend_gtk4agg.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
import cairo # Presence of cairo is already checked by _backend_gtk.
88

99

10-
class FigureCanvasGTK4Agg(backend_gtk4.FigureCanvasGTK4,
11-
backend_agg.FigureCanvasAgg):
12-
def __init__(self, figure):
13-
backend_gtk4.FigureCanvasGTK4.__init__(self, figure)
10+
class FigureCanvasGTK4Agg(backend_agg.FigureCanvasAgg,
11+
backend_gtk4.FigureCanvasGTK4):
1412

1513
def on_draw_event(self, widget, ctx):
1614
scale = self.device_pixel_ratio
@@ -32,12 +30,6 @@ def on_draw_event(self, widget, ctx):
3230

3331
return False
3432

35-
def draw(self):
36-
# Call these explicitly because GTK's draw is a GObject method which
37-
# isn't cooperative with Python class methods.
38-
backend_agg.FigureCanvasAgg.draw(self)
39-
backend_gtk4.FigureCanvasGTK4.draw(self)
40-
4133

4234
@_api.deprecated("3.6", alternative="backend_gtk4.FigureManagerGTK4")
4335
class FigureManagerGTK4Agg(backend_gtk4.FigureManagerGTK4):

lib/matplotlib/backends/backend_gtk4cairo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ def set_context(self, ctx):
1111
self.gc.ctx = backend_cairo._to_context(ctx)
1212

1313

14-
class FigureCanvasGTK4Cairo(backend_gtk4.FigureCanvasGTK4,
15-
backend_cairo.FigureCanvasCairo):
14+
class FigureCanvasGTK4Cairo(backend_cairo.FigureCanvasCairo,
15+
backend_gtk4.FigureCanvasGTK4):
1616
_context_is_scaled = True
1717

1818
def on_draw_event(self, widget, ctx):

lib/matplotlib/backends/backend_macosx.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ class TimerMac(_macosx.Timer, TimerBase):
1717
# completely implemented at the C-level (in _macosx.Timer)
1818

1919

20-
class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg):
20+
class FigureCanvasMac(FigureCanvasAgg, _macosx.FigureCanvas, FigureCanvasBase):
2121
# docstring inherited
2222

23+
# Ideally this class would be `class FCMacAgg(FCAgg, FCMac)`
24+
# (FC=FigureCanvas) where FCMac would be an ObjC-implemented mac-specific
25+
# class also inheriting from FCBase (this is the approach with other GUI
26+
# toolkits). However, writing an extension type inheriting from a Python
27+
# base class is slightly tricky (the extension type must be a heap type),
28+
# and we can just as well lift the FCBase base up one level, keeping it *at
29+
# the end* to have the right method resolution order.
30+
2331
# Events such as button presses, mouse movements, and key presses
2432
# are handled in the C code and the base class methods
2533
# button_press_event, button_release_event, motion_notify_event,
@@ -30,9 +38,7 @@ class FigureCanvasMac(_macosx.FigureCanvas, FigureCanvasAgg):
3038
manager_class = _api.classproperty(lambda cls: FigureManagerMac)
3139

3240
def __init__(self, figure):
33-
FigureCanvasBase.__init__(self, figure)
34-
width, height = self.get_width_height()
35-
_macosx.FigureCanvas.__init__(self, width, height)
41+
super().__init__(figure=figure)
3642
self._draw_pending = False
3743
self._is_drawing = False
3844

lib/matplotlib/backends/backend_qt.py

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -160,46 +160,6 @@ def _create_qApp():
160160
return app
161161

162162

163-
def _allow_super_init(__init__):
164-
"""
165-
Decorator for ``__init__`` to allow ``super().__init__`` on PySide2.
166-
"""
167-
168-
if QT_API in ["PyQt5", "PyQt6"]:
169-
170-
return __init__
171-
172-
else:
173-
# To work around lack of cooperative inheritance in PySide2 and
174-
# PySide6, when calling FigureCanvasQT.__init__, we temporarily patch
175-
# QWidget.__init__ by a cooperative version, that first calls
176-
# QWidget.__init__ with no additional arguments, and then finds the
177-
# next class in the MRO with an __init__ that does support cooperative
178-
# inheritance (i.e., not defined by the PyQt4 or sip, or PySide{,2,6}
179-
# or Shiboken packages), and manually call its `__init__`, once again
180-
# passing the additional arguments.
181-
182-
qwidget_init = QtWidgets.QWidget.__init__
183-
184-
def cooperative_qwidget_init(self, *args, **kwargs):
185-
qwidget_init(self)
186-
mro = type(self).__mro__
187-
next_coop_init = next(
188-
cls for cls in mro[mro.index(QtWidgets.QWidget) + 1:]
189-
if cls.__module__.split(".")[0] not in [
190-
"PySide2", "PySide6", "Shiboken",
191-
])
192-
next_coop_init.__init__(self, *args, **kwargs)
193-
194-
@functools.wraps(__init__)
195-
def wrapper(self, *args, **kwargs):
196-
with cbook._setattr_cm(QtWidgets.QWidget,
197-
__init__=cooperative_qwidget_init):
198-
__init__(self, *args, **kwargs)
199-
200-
return wrapper
201-
202-
203163
class TimerQT(TimerBase):
204164
"""Subclass of `.TimerBase` using QTimer events."""
205165

@@ -229,7 +189,7 @@ def _timer_stop(self):
229189
self._timer.stop()
230190

231191

232-
class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
192+
class FigureCanvasQT(FigureCanvasBase, QtWidgets.QWidget):
233193
required_interactive_framework = "qt"
234194
_timer_cls = TimerQT
235195
manager_class = _api.classproperty(lambda cls: FigureManagerQT)
@@ -244,7 +204,6 @@ class FigureCanvasQT(QtWidgets.QWidget, FigureCanvasBase):
244204
]
245205
}
246206

247-
@_allow_super_init
248207
def __init__(self, figure=None):
249208
_create_qApp()
250209
super().__init__(figure=figure)

lib/matplotlib/backends/backend_qtagg.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616

1717
class FigureCanvasQTAgg(FigureCanvasAgg, FigureCanvasQT):
1818

19-
def __init__(self, figure=None):
20-
# Must pass 'figure' as kwarg to Qt base class.
21-
super().__init__(figure=figure)
22-
2319
def paintEvent(self, event):
2420
"""
2521
Copy the image from the Agg canvas to the qt.drawable.

lib/matplotlib/backends/backend_qtcairo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .qt_compat import QT_API, _enum, _setDevicePixelRatio
66

77

8-
class FigureCanvasQTCairo(FigureCanvasQT, FigureCanvasCairo):
8+
class FigureCanvasQTCairo(FigureCanvasCairo, FigureCanvasQT):
99
def draw(self):
1010
if hasattr(self._renderer.gc, "ctx"):
1111
self._renderer.dpi = self.figure.dpi

lib/matplotlib/backends/backend_wxcairo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def get_canvas(self, fig):
1414
return FigureCanvasWxCairo(self, -1, fig)
1515

1616

17-
class FigureCanvasWxCairo(_FigureCanvasWxBase, FigureCanvasCairo):
17+
class FigureCanvasWxCairo(FigureCanvasCairo, _FigureCanvasWxBase):
1818
"""
1919
The FigureCanvas contains the figure and does event handling.
2020

src/_macosx.m

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ static CGFloat _get_device_scale(CGContextRef cr)
294294
View* view;
295295
} FigureCanvas;
296296

297+
static PyTypeObject FigureCanvasType;
298+
297299
static PyObject*
298300
FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
299301
{
@@ -307,14 +309,27 @@ static CGFloat _get_device_scale(CGContextRef cr)
307309
static int
308310
FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds)
309311
{
310-
int width;
311-
int height;
312312
if (!self->view) {
313313
PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
314314
return -1;
315315
}
316-
if (!PyArg_ParseTuple(args, "ii", &width, &height)) { return -1; }
317-
316+
PyObject *builtins = NULL,
317+
*super_obj = NULL,
318+
*super_init = NULL,
319+
*init_res = NULL,
320+
*wh = NULL;
321+
// super(FigureCanvasMac, self).__init__(*args, **kwargs)
322+
if (!(builtins = PyImport_AddModule("builtins")) // borrowed.
323+
|| !(super_obj = PyObject_CallMethod(builtins, "super", "OO", &FigureCanvasType, self))
324+
|| !(super_init = PyObject_GetAttrString(super_obj, "__init__"))
325+
|| !(init_res = PyObject_Call(super_init, args, kwds))) {
326+
goto exit;
327+
}
328+
int width, height;
329+
if (!(wh = PyObject_CallMethod((PyObject*)self, "get_width_height", ""))
330+
|| !PyArg_ParseTuple(wh, "ii", &width, &height)) {
331+
goto exit;
332+
}
318333
NSRect rect = NSMakeRect(0.0, 0.0, width, height);
319334
self->view = [self->view initWithFrame: rect];
320335
self->view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
@@ -326,7 +341,13 @@ static CGFloat _get_device_scale(CGContextRef cr)
326341
owner: self->view
327342
userInfo: nil]];
328343
[self->view setCanvas: (PyObject*)self];
329-
return 0;
344+
345+
exit:
346+
Py_XDECREF(super_obj);
347+
Py_XDECREF(super_init);
348+
Py_XDECREF(init_res);
349+
Py_XDECREF(wh);
350+
return PyErr_Occurred() ? -1 : 0;
330351
}
331352

332353
static void

0 commit comments

Comments
 (0)