Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ca9a779

Browse files
authored
Merge pull request #23363 from anntzer/coop-mi
Simplify FigureCanvas multiple inheritance init by swapping bases order.
2 parents ad34fff + bac9962 commit ca9a779

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
@@ -298,6 +298,8 @@ static CGFloat _get_device_scale(CGContextRef cr)
298298
View* view;
299299
} FigureCanvas;
300300

301+
static PyTypeObject FigureCanvasType;
302+
301303
static PyObject*
302304
FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
303305
{
@@ -311,14 +313,27 @@ static CGFloat _get_device_scale(CGContextRef cr)
311313
static int
312314
FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds)
313315
{
314-
int width;
315-
int height;
316316
if (!self->view) {
317317
PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL");
318318
return -1;
319319
}
320-
if (!PyArg_ParseTuple(args, "ii", &width, &height)) { return -1; }
321-
320+
PyObject *builtins = NULL,
321+
*super_obj = NULL,
322+
*super_init = NULL,
323+
*init_res = NULL,
324+
*wh = NULL;
325+
// super(FigureCanvasMac, self).__init__(*args, **kwargs)
326+
if (!(builtins = PyImport_AddModule("builtins")) // borrowed.
327+
|| !(super_obj = PyObject_CallMethod(builtins, "super", "OO", &FigureCanvasType, self))
328+
|| !(super_init = PyObject_GetAttrString(super_obj, "__init__"))
329+
|| !(init_res = PyObject_Call(super_init, args, kwds))) {
330+
goto exit;
331+
}
332+
int width, height;
333+
if (!(wh = PyObject_CallMethod((PyObject*)self, "get_width_height", ""))
334+
|| !PyArg_ParseTuple(wh, "ii", &width, &height)) {
335+
goto exit;
336+
}
322337
NSRect rect = NSMakeRect(0.0, 0.0, width, height);
323338
self->view = [self->view initWithFrame: rect];
324339
self->view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
@@ -330,7 +345,13 @@ static CGFloat _get_device_scale(CGContextRef cr)
330345
owner: self->view
331346
userInfo: nil]];
332347
[self->view setCanvas: (PyObject*)self];
333-
return 0;
348+
349+
exit:
350+
Py_XDECREF(super_obj);
351+
Py_XDECREF(super_init);
352+
Py_XDECREF(init_res);
353+
Py_XDECREF(wh);
354+
return PyErr_Occurred() ? -1 : 0;
334355
}
335356

336357
static void

0 commit comments

Comments
 (0)