-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Saving png figures matplotlib 3.3.0 results in "AttributeError: __delete__" #252
Comments
this is also a problem if you I think is an important bug. That error is a bit opaque to me though. @tacaswell @martinRenou @thomasaarholt do any of you have thoughts on where to start here? |
I am not sure. I find this odd that it worked on 3.2 and not 3.3. Nothing changed Python side between those two versions. It might be a Python dependency change. @ianhi did you investigate on what this function does and why it looks for this delete attribute? |
I have not, I got confused by all the context managers/wrappers that were happening. Though looking at it again briefly it looks as though there was a recent commit to the Though what difference that would make to this backend and not others is beyond me. |
Also from a few lines up there is this comment (ref)
So I guess being a subclass of |
Maybe, I don't really know. @tacaswell do you know what this code is and how we should properly fix it? Would a |
Did a bit of bisecting: |
Agree that this is a critical bug. @ianhi Please always prefer to post text rather than screen shots. This does need to be fixed on the mpl side, but we need some help from someone who understands why our attempts to identify descriptors is failing with traitlets. https://github.com/matplotlib/matplotlib/blob/bb71b00ac1a16c41054c2a5907d04f65da3e8565/lib/matplotlib/cbook/__init__.py#L2044-L2085 is the code in question. We are monkey patching attributes internally (sometimes just resetting normal attributes, sometimes shadowing methods) and we want to safely reset it back to what it was before. In same cases we need to do a Very specifically, this logic: orig = getattr(obj, attr, sentinel)
if attr in obj.__dict__ or orig is sentinel:
# if we are pulling from the instance dict or the object
# does not have this attribute we can trust the above
origs[attr] = orig
else:
# if the attribute is not in the instance dict it must be
# from the class level
cls_orig = getattr(type(obj), attr)
# if we are dealing with a property (but not a general descriptor)
# we want to set the original value back.
if isinstance(cls_orig, property):
origs[attr] = orig
# otherwise this is _something_ we are going to shadow at
# the instance dict level from higher up in the MRO. We
# are going to assume we can delattr(obj, attr) to clean
# up after ourselves. It is possible that this code will
# fail if used with a non-property custom descriptor which
# implements __set__ (and __delete__ does not act like a
# stack). However, this is an internal tool and we do not
# currently have any custom descriptors.
else:
origs[attr] = sentinel is not correctly identifying traitlets, how to we fix that? There was a more general version of the code that went in at matplotlib/matplotlib#17561 |
Unfortunately, I don't qualify for this. But maybe @martinRenou would know who to ask?
Could that code check if the ipympl backend is in use, and if so use the version that was in Alternatively, would it be reasonable for traitlets to implement Minimal reproduction with only traitlets:from traitlets import HasTraits, Unicode
class Identity(HasTraits):
username = Unicode()
ident = Identity()
delattr(ident, 'username') gives:
|
Traitlets gets updated at a very slow pace, we might want to monkeypatch it in ipympl, doing something like: from traitlets import HasTraits as OrigHasTraits
class HasTraits(OrigHasTraits):
def __delete__(self, attribute):
pass I wonder what this |
Unfortunately, I think this may be primarily a problem with traits. If I extend the example it looks as though it only happens with traits: from traitlets import HasTraits, Unicode
class Identity(HasTraits):
username = Unicode()
ident = Identity()
#no errors
setattr(ident, 'test-attr', 'test')
delattr(ident,'test-attr')
# errors
delattr(ident, 'username') ---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-17-f01352160376> in <module>
9
10 # errors
---> 11 delattr(ident, 'username')
AttributeError: __delete__ Any idea which attributes are being accessed and deleted by |
I added a print statement to diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py
index 2b52dd414..da3d5c24a 100644
--- a/lib/matplotlib/cbook/__init__.py
+++ b/lib/matplotlib/cbook/__init__.py
@@ -2091,6 +2091,7 @@ def _setattr_cm(obj, **kwargs):
finally:
for attr, orig in origs.items():
if orig is sentinel:
+ print(attr)
delattr(obj, attr)
else:
setattr(obj, attr, orig) and it looks as though attribute in question is ipympl/ipympl/backend_nbagg.py Line 202 in 58bdb1e
This is true for both
|
Implementing |
@tacaswell sorry I keep diverting away from the final solution of implementing better handling in matplotlib, unfortunately I don't think I can help there. But! I do have a workaround that can be implemented in ipympl that seems to give all the correct behaviors and I don't think will messing with the context manager: It turns out that diff --git a/ipympl/backend_nbagg.py b/ipympl/backend_nbagg.py
index de37bab..8b88609 100644
--- a/ipympl/backend_nbagg.py
+++ b/ipympl/backend_nbagg.py
@@ -199,7 +199,6 @@ class Canvas(DOMWidget, FigureCanvasWebAggCore):
_current_image_mode = Unicode()
_dpi_ratio = Float(1.0)
_is_idle_drawing = Bool()
- _is_saving = Bool()
_button = Any()
_key = Any()
_lastx = Any() Which allows %matplotlib widget
import matplotlib
from matplotlib import pyplot as plt
with plt.ioff():
fig = plt.figure()
plt.plot([0,1],[0,1])
fig.savefig('test.png')
display(fig)
print(f'Matplotlib version: {matplotlib.__version__}')
print(fig.canvas._is_saving) # no attribute error!! I know this isn't the optimal solution but how would you feel about using to fix the immediate problem? |
That is very odd, how come the Matplotlib logic fails with the Isn't the |
I would also take out |
and if this fixes the problem, then I would say this is the optimal solution as it does not involve core Matplotlib picking up any more complexity and reduces the exposed public surface on the ipympl side. |
Because we are not trying to use other traits in the "set / unset attributes" context manager. |
I believe I am still having this issue. I am using matplotlib version 3.3.0 and am trying to execute the following code in JupyterLab:
It seems to me as though this error is relevant to the discussion on here, despite this issue being closed. If I am doing something wrong, I do not see what it is. |
For me it was fixed with the new release 0.5.8 after the fix from @ianhi . But I have to admit I moved on to the next matplotlib release already. Are you using ipympl 0.5.8? Edit: I just saw on the readme page that ipympl 0.5.8 requires matplotlib 3.3.1. Couldn't you move to this one? Or is 3.3.0 necessary? |
That must be it – I can definitely update. Thank you @scimax for your help! |
Describe the issue
When trying to save png figures with ipympl, interactive widgets, and the latest matplotlib (3.3.0), the saving fails with the following error message:
Saving figures as
pdf
orpng
figures with an older Matplotlib version (3.2.0) works well.Versions
Matplotlib version: 3.3.0
-->
The text was updated successfully, but these errors were encountered: