diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index 05631ef4ca45..873d46c9997c 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -2035,20 +2035,28 @@ def _setattr_cm(obj, **kwargs): origs = {} for attr in kwargs: 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 - elif isinstance(cls_orig, types.FunctionType): - origs[attr] = sentinel + # 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: - raise ValueError( - f"trying to set {attr} which is not a method, " - "property, or instance level attribute" - ) + origs[attr] = sentinel try: for attr, val in kwargs.items(): diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 4e4457e2a392..f6429cf9ecc5 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -666,6 +666,14 @@ def __init__(self): def meth(self): ... + @classmethod + def classy(klass): + ... + + @staticmethod + def static(): + ... + @property def prop(self): return self._p @@ -696,6 +704,10 @@ def verify_pre_post_state(obj): assert not hasattr(obj, 'extra') assert obj.prop == 'p' assert obj.monkey == other.meth + assert obj.cls_level is A.cls_level + assert 'cls_level' not in obj.__dict__ + assert 'classy' not in obj.__dict__ + assert 'static' not in obj.__dict__ a = B() @@ -705,7 +717,8 @@ def verify_pre_post_state(obj): a, prop='squirrel', aardvark='moose', meth=lambda: None, override='boo', extra='extra', - monkey=lambda: None): + monkey=lambda: None, cls_level='bob', + classy='classy', static='static'): # because we have set a lambda, it is normal attribute access # and the same every time assert a.meth is a.meth @@ -715,9 +728,8 @@ def verify_pre_post_state(obj): assert a.extra == 'extra' assert a.prop == 'squirrel' assert a.monkey != other.meth + assert a.cls_level == 'bob' + assert a.classy == 'classy' + assert a.static == 'static' verify_pre_post_state(a) - - with pytest.raises(ValueError): - with cbook._setattr_cm(a, cls_level='bob'): - pass
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: