Cách xóa các dòng trong một âm mưu Matplotlib


84

Làm cách nào để xóa một dòng (hoặc các dòng) của trục matplotlib theo cách mà nó thực sự được thu gom rác và giải phóng bộ nhớ trở lại? Đoạn mã dưới đây dường như xóa dòng, nhưng không bao giờ giải phóng bộ nhớ (ngay cả với các lệnh gọi rõ ràng đến gc.collect())

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e7))
# large so you can easily see the memory footprint on the system monitor.
fig = pyplot.Figure()
ax  = pyplot.add_subplot(1, 1, 1)
lines = ax.plot(a) # this uses up an additional 230 Mb of memory.
# can I get the memory back?
l = lines[0]
l.remove()
del l
del lines
# not releasing memory
ax.cla() # this does release the memory, but also wipes out all other lines.

Vậy có cách nào để xóa một dòng khỏi trục và lấy lại bộ nhớ không? Giải pháp tiềm năng này cũng không hoạt động.

Câu trả lời:


70

Tôi đang cho thấy rằng sự kết hợp của lines.pop(0) l.remove()del lthực hiện thủ thuật.

from matplotlib import pyplot
import numpy, weakref
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

l = lines.pop(0)
wl = weakref.ref(l)  # create a weak reference to see if references still exist
#                      to this object
print wl  # not dead
l.remove()
print wl  # not dead
del l
print wl  # dead  (remove either of the steps above and this is still live)

Tôi đã kiểm tra tập dữ liệu lớn của bạn và việc giải phóng bộ nhớ cũng được xác nhận trên màn hình hệ thống.

Tất nhiên, cách đơn giản hơn (khi không gặp sự cố) sẽ là bật nó khỏi danh sách và gọi removeđối tượng dòng mà không tạo tham chiếu khó đến nó:

lines.pop(0).remove()

Tôi đã chạy mã của bạn và tôi nhận được: [8:37 tối] @flattop: ~ / Desktop / sandbox> python delete_lines.py <yếuref tại 0x8dd348c; thành 'Line2D' tại 0x8dd43ec> <yếuref tại 0x8dd348c; thành 'Line2D' tại 0x8dd43ec> <yếuref tại 0x8dd348c; thành 'Line2D' tại 0x8dd43ec> Tôi đang sử dụng matplotlib phiên bản 0.99.1.1 trong ubuntu 10.04
David Morton

1
@David Morton Tôi vừa hạ cấp xuống 0.99.1 và bây giờ tôi tái tạo sự cố của bạn. Tôi đoán tôi chỉ có thể khuyên bạn nên nâng cấp lên 1.0.1. Có rất nhiều bản sửa lỗi kể từ 0.99.x
Paul

1
Vấn đề ở đây có thể là một vấn đề của các tham chiếu quanh quẩn khi chúng không nên. Tôi cá rằng OP đã sử dụng IPython để kiểm tra mọi thứ. Hãy xem câu trả lời của tôi.
Vorticity

66

Đây là một lời giải thích rất dài mà tôi đã gõ cho một đồng nghiệp của mình. Tôi nghĩ rằng nó cũng sẽ hữu ích ở đây. Hãy kiên nhẫn, mặc dù. Tôi đi đến vấn đề thực sự mà bạn đang gặp phải cuối cùng. Cũng giống như một đoạn giới thiệu, đó là một vấn đề của việc có thêm tham chiếu đến các Line2Dđối tượng của bạn xung quanh.

CẢNH BÁO: Một lưu ý khác trước khi chúng ta đi sâu vào. Nếu bạn đang sử dụng IPython để kiểm tra điều này, thì IPython giữ các tham chiếu của riêng mình và không phải tất cả chúng đều là yếu. Vì vậy, thử nghiệm thu gom rác trong IPython không hoạt động. Nó chỉ làm rối ren vấn đề.

Được rồi, chúng ta bắt đầu. Mỗi matplotlibđối tượng ( Figure, Axesv.v.) cung cấp quyền truy cập vào các nghệ sĩ con của nó thông qua các thuộc tính khác nhau. Ví dụ sau đây khá dài, nhưng sẽ sáng tỏ.

Chúng tôi bắt đầu bằng cách tạo một Figuređối tượng, sau đó thêm một Axesđối tượng vào hình đó. Lưu ý rằng axfig.axes[0]là cùng một đối tượng (giống nhau id()).

>>> #Create a figure
>>> fig = plt.figure()
>>> fig.axes
[]

>>> #Add an axes object
>>> ax = fig.add_subplot(1,1,1)

>>> #The object in ax is the same as the object in fig.axes[0], which is 
>>> #   a list of axes objects attached to fig 
>>> print ax
Axes(0.125,0.1;0.775x0.8)
>>> print fig.axes[0]
Axes(0.125,0.1;0.775x0.8)  #Same as "print ax"
>>> id(ax), id(fig.axes[0])
(212603664, 212603664) #Same ids => same objects

Điều này cũng mở rộng đến các dòng trong một đối tượng trục:

>>> #Add a line to ax
>>> lines = ax.plot(np.arange(1000))

>>> #Lines and ax.lines contain the same line2D instances 
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]
>>> print ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]

>>> print lines[0]
Line2D(_line0)
>>> print ax.lines[0]
Line2D(_line0)

>>> #Same ID => same object
>>> id(lines[0]), id(ax.lines[0])
(216550352, 216550352)

Nếu bạn gọi plt.show()bằng cách sử dụng những gì đã làm ở trên, bạn sẽ thấy một hình chứa một tập hợp các trục và một dòng:

Một hình chứa một tập hợp các trục và một đường đơn

Bây giờ, mặc dù chúng ta đã thấy rằng nội dung của linesax.linesđều giống nhau, nhưng điều rất quan trọng cần lưu ý là đối tượng được tham chiếu bởi linesbiến không giống với đối tượng được cung cấp lại bởi ax.linescó thể thấy như sau:

>>> id(lines), id(ax.lines)
(212754584, 211335288)

Do đó, việc xóa một phần tử khỏi linessẽ không ảnh hưởng gì đến cốt truyện hiện tại, nhưng xóa một phần tử khỏi ax.linessẽ xóa dòng đó khỏi cốt truyện hiện tại. Vì thế:

>>> #THIS DOES NOTHING:
>>> lines.pop(0)

>>> #THIS REMOVES THE FIRST LINE:
>>> ax.lines.pop(0)

Vì vậy, nếu bạn chạy dòng mã thứ hai, bạn sẽ xóa Line2Dđối tượng có trong ax.lines[0]ô hiện tại và nó sẽ biến mất. Lưu ý rằng điều này cũng có thể được thực hiện thông qua ax.lines.remove()nghĩa là bạn có thể lưu một Line2Dthể hiện trong một biến, sau đó chuyển nó ax.lines.remove()để xóa dòng đó, như sau:

>>> #Create a new line
>>> lines.append(ax.plot(np.arange(1000)/2.0))
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

Một hình chứa một tập hợp các trục và hai đường

>>> #Remove that new line
>>> ax.lines.remove(lines[0])
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84dx3>]

Một hình chứa một tập hợp các trục và chỉ có dòng thứ hai

Tất cả những điều trên hoạt động fig.axescũng như nó hoạt động choax.lines

Bây giờ, vấn đề thực sự ở đây. Nếu chúng tôi lưu trữ tham chiếu có trong ax.lines[0]một weakref.refđối tượng, sau đó cố gắng xóa nó, chúng tôi sẽ nhận thấy rằng nó không được thu thập rác:

>>> #Create weak reference to Line2D object
>>> from weakref import ref
>>> wr = ref(ax.lines[0])
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

>>> #Delete the line from the axes
>>> ax.lines.remove(wr())
>>> ax.lines
[]

>>> #Test weakref again
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

Tham khảo vẫn còn sống! Tại sao? Điều này là do vẫn còn một tham chiếu khác đến Line2Dđối tượng mà tham chiếu wrtrỏ tới. Hãy nhớ làm thế nào lineskhông có cùng một ID ax.linesnhưng chứa các phần tử giống nhau? Đó là vấn đề.

>>> #Print out lines
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

To fix this problem, we simply need to delete `lines`, empty it, or let it go out of scope.

>>> #Reinitialize lines to empty list
>>> lines = []
>>> print lines
[]
>>> print wr
<weakref at 0xb758af8; dead>

Vì vậy, đạo lý của câu chuyện là, hãy làm sạch sau khi chính mình. Nếu bạn mong đợi thứ gì đó được thu gom nhưng không có, bạn có khả năng để lại một tài liệu tham khảo đi chơi ở đâu đó.


2
Chính xác những gì tôi cần. Tôi đang vẽ hàng nghìn bản đồ, mỗi bản đồ có một biểu đồ phân tán phía trên hình chiếu bản đồ thế giới. Họ mất 3 giây mỗi lần! Bằng cách sử dụng lại hình vẽ với bản đồ đã được vẽ và xuất bộ sưu tập kết quả từ ax.collections, tôi đã giảm nó xuống còn 1/3 giây. Cảm ơn!
GaryBishop

3
Tôi nghĩ rằng điều này không còn cần thiết trong các phiên bản hiện tại của mpl. Nghệ sĩ có một remove()chức năng sẽ làm sạch chúng ra khỏi mặt mpl của mọi thứ và sau đó bạn chỉ cần theo dõi các tài liệu tham khảo của mình.
tacaswell

2
Hả, có biết phiên bản matplotlib này thay đổi như thế nào không?
Vorticity

Nhận thấy điều này hữu ích khi sử dụng một loạt các âm mưu trong hoạt ảnh matplotlib. Nếu không, bạn sẽ sử dụng một lượng bộ nhớ rất lớn. Bây giờ để làm cho điều này nhanh hơn.
Danny Staple

14

Tôi đã thử rất nhiều câu trả lời khác nhau trong các diễn đàn khác nhau. Tôi đoán nó phụ thuộc vào máy bạn đang phát triển. Nhưng tôi đã sử dụng câu lệnh

ax.lines = []

và hoạt động hoàn hảo. Tôi không sử dụng cla()vì nó xóa tất cả các định nghĩa tôi đã đưa ra cho cốt truyện

Ví dụ.

pylab.setp(_self.ax.get_yticklabels(), fontsize=8)

nhưng tôi đã thử xóa các dòng nhiều lần. Cũng sử dụng thư viện yếuref để kiểm tra tham chiếu đến dòng đó trong khi tôi đang xóa nhưng không có gì hiệu quả với tôi.

Hy vọng cái này hiệu quả với người khác = D


Vấn đề ở đây có thể là một vấn đề của các tham chiếu quanh quẩn khi chúng không nên. Tôi cá rằng OP đã sử dụng IPython để kiểm tra mọi thứ. Hãy xem câu trả lời của tôi.
Vorticity

5

(sử dụng ví dụ tương tự như anh chàng ở trên)

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

for i, line in enumerate(ax.lines):
    ax.lines.pop(i)
    line.remove()

1

Hy vọng rằng điều này có thể giúp những người khác: Các ví dụ trên sử dụng ax.lines. Với mpl gần đây hơn (3.3.1), có ax.get_lines(). Điều này bỏ qua nhu cầu gọi điệnax.lines=[]

for line in ax.get_lines(): # ax.lines:
    line.remove()
# ax.lines=[] # needed to complete removal when using ax.lines
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.