Keywords: Matplotlib | Figure Management | State Machine | Resource Release | Python Visualization
Abstract: This article provides a comprehensive examination of the "RuntimeWarning: More than 20 figures have been opened" mechanism in Matplotlib, detailing the reference management principles of the pyplot state machine for figure objects. By comparing the effectiveness of different cleanup methods, it systematically explains the applicable scenarios and differences between plt.cla(), plt.clf(), and plt.close(), accompanied by practical code examples demonstrating effective figure resource management to prevent memory leaks and performance issues. From the perspective of system resource management, the article also illustrates the impact of file descriptor limits on applications through reference cases, offering complete technical guidance for Python data visualization development.
Problem Background and Phenomenon Analysis
When using Matplotlib for data visualization, developers frequently encounter a perplexing warning: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface are retained until explicitly closed and may consume too much memory. The appearance of this warning often contradicts developer expectations, particularly when developers believe they have properly handled figure objects.
State Machine Reference Mechanism Analysis
Matplotlib's pyplot module maintains a state machine responsible for managing currently active figures and axes. When creating figures using plt.subplots(), the state machine automatically records these figure instances as current figures. Even if developers remove their own references through del fig, the state machine still retains references to these figures to prevent them from being garbage collected.
This design ensures that pyplot can correctly track currently active figures, but it also introduces potential memory management issues. The state machine maintains reference counts for all open figures through the _pylab_helpers.Gcf data structure, and these references are only removed when explicit close methods are called.
Comparative Analysis of Cleanup Methods
For figure cleanup, Matplotlib provides multiple methods, each with different scopes and applicable scenarios:
The plt.cla() method is specifically designed for clearing axes of the current active figure. It removes all plotting elements from the axes, including lines, markers, text, etc., but preserves the axis framework and figure window itself. This method is suitable for situations requiring multiple different datasets to be plotted in the same figure.
import matplotlib.pyplot as plt
# Create figure and plot data
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
plt.savefig('plot1.png')
# Clear axes to reuse figure
plt.cla()
ax.plot([1, 2, 3], [1, 8, 27])
plt.savefig('plot2.png')
plt.close(fig)
The plt.clf() method provides more thorough cleanup, clearing all axes and plotting content from the current figure while keeping the figure window open. This approach is appropriate for scenarios requiring complete figure reset while maintaining the window framework.
import matplotlib.pyplot as plt
# Create complex figure
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot([1, 2, 3], [1, 2, 3])
ax2.bar(['A', 'B', 'C'], [10, 20, 15])
plt.savefig('complex_plot.png')
# Completely clear figure
plt.clf()
# Figure is now empty and can be reused
plt.close(fig)
Figure Closure and Resource Release
The plt.close() method is crucial for resolving "too many open figures" warnings. This method not only closes the figure window but, more importantly, removes figure references from the pyplot state machine, allowing the system to reclaim associated resources.
Calling plt.close(fig) with specific figure instances enables precise control over resource release timing:
import matplotlib.pyplot as plt
import numpy as np
# Batch figure generation example
for i in range(25):
fig, ax = plt.subplots()
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x + i*0.1)
ax.plot(x, y)
# Save figure and immediately close
plt.savefig(f'wave_{i:02d}.png')
plt.close(fig) # Critical step: remove reference from state machine
print("All figures generated and properly closed")
For situations requiring closing all figures at once, the plt.close('all') command can be used. This is particularly useful in interactive development or script exception handling.
Extended Perspective on System Resource Management
"Too many open files" type issues are not limited to Matplotlib figure management but exist similarly in broader system programming contexts. Referring to the Too many open files error encountered by Nextcloud synchronization tools, we can observe similar problem patterns.
In Unix-like systems, each process has file descriptor limits, typically defaulting to 1024. When applications open numerous files or network connections simultaneously, they can easily reach this limit. Solutions include adjusting system-level ulimit -n settings or optimizing application file handling logic.
Matplotlib's figure management can be viewed as a specific instance of this general problem. Each open figure consumes system resources, including memory and potential GUI resources. Through appropriate resource management strategies, such issues can be prevented.
Best Practice Recommendations
Based on understanding the Matplotlib state machine mechanism, we recommend the following best practices:
In scripts generating figures in batches, always call plt.close(fig) immediately after saving figures. This ensures figure references are promptly removed from the state machine, avoiding cumulative effects.
For interactive development environments, regularly use plt.close('all') to clean up all figures, particularly before starting new analysis tasks.
Consider using object-oriented approaches to manage figure lifecycles, encapsulating figure creation, usage, and destruction within clear contexts:
import matplotlib.pyplot as plt
class FigureManager:
def __init__(self):
self.figures = []
def create_figure(self, *args, **kwargs):
fig, ax = plt.subplots(*args, **kwargs)
self.figures.append(fig)
return fig, ax
def close_all(self):
for fig in self.figures:
plt.close(fig)
self.figures.clear()
# Usage example
manager = FigureManager()
for i in range(10):
fig, ax = manager.create_figure()
# Perform plotting operations
plt.savefig(f'output_{i}.png')
# Clean up all figures after task completion
manager.close_all()
By adopting these strategies, developers can effectively manage Matplotlib figure resources, avoid interference from warning messages, and ensure application memory usage remains within reasonable bounds.