-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Description
Bug summary
The descriptor seems to get called twice and thus the expensive instance._get_tick(major=True)
is executed twice. Ping @anntzer, who helped craft the implementation.
Code for reproduction
When adding the following into _LazyTickList.__get__
if self._major:
# <snip>
import traceback, sys
print(f"\n*** Initializing major ticks on {type(instance)} **\n")
traceback.print_stack(file=sys.stdout, limit=6)
# </snip>
instance.majorTicks = []
tick = instance._get_tick(major=True)
instance.majorTicks.append(tick)
return instance.majorTicks
we see that it is called twice per Axis:
*** Initializing major ticks on <class 'matplotlib.axis.XAxis'> **
File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1399, in clear
self.__clear()
File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1315, in __clear
self.grid(False) # Disable grid on init to use rcParameter
File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 3295, in grid
self.xaxis.grid(visible, which=which, **kwargs)
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 1726, in grid
self.set_tick_params(which='major', **gridkw)
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 984, in set_tick_params
for tick in self.majorTicks:
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 549, in __get__
traceback.print_stack(file=sys.stdout, limit=6)
*** Initializing major ticks on <class 'matplotlib.axis.XAxis'> **
File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 1315, in __clear
self.grid(False) # Disable grid on init to use rcParameter
File "/home/tim/git/matplotlib/lib/matplotlib/axes/_base.py", line 3295, in grid
self.xaxis.grid(visible, which=which, **kwargs)
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 1726, in grid
self.set_tick_params(which='major', **gridkw)
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 984, in set_tick_params
for tick in self.majorTicks:
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 553, in __get__
instance.majorTicks.append(tick)
File "/home/tim/git/matplotlib/lib/matplotlib/axis.py", line 549, in __get__
traceback.print_stack(file=sys.stdout, limit=6)
[... same repeated for YAxis]
Looking at the second traceback it seems that the line instance.majorTicks.append(tick)
re-triggers the descriptor, even though we have previously set instance.majorTicks = []
. I would have expected that at the time, the name instance.majorTicks
is already re-bound to the list (which is sort of the purpose of the init-empty-and-append acrobatics - see the code comment above). But then again, this is higher magic and we might be hitting some implementation details of descriptors.
This observation may have two implications:
- We're apparently running the expensive
instance._get_tick(major=True)
twice. This should be fixed. - It may be that init-empty-and-append acrobatics does not fulfill its intended purpose of providing
instance.majorTicks
to the implementation of_get_tick
.
Possible alternative
have a dummy empty list for _get_tick
- I assume it's anyway only trying to read and not modify or hold references.
And then create a new list with the tick. This prevents read-access to instance.majorTicks
and re-calling the descriptor. i.e. replace
instance.majorTicks = []
tick = instance._get_tick(major=True)
instance.majorTicks.append(tick)
return instance.majorTicks
by
instance.majorTicks = []
tick = instance._get_tick(major=True)
instance.majorTicks = [tick]
return instance.majorTicks
Not sure how that works when _get_tick
accesses majorTicks
but it should not make it worse there, and we improve inside __get__
.
Performance measurement:
While performance measurement is a bit tricky, I think fair timings are
before (ms) | after (ms) | change | |
---|---|---|---|
plt.subplots() | 38±2 | 31±1 | -18% |
plt.subplots(10, 10) | 1420±13 | 1063±7 | -25% |