Skip to content

Commit d479a91

Browse files
authored
Merge pull request #17649 from tacaswell/fix_setattr_cm
MNT: make _setattr_cm more forgiving
2 parents 4f65a8a + e11316a commit d479a91

File tree

2 files changed

+32
-12
lines changed

2 files changed

+32
-12
lines changed

lib/matplotlib/cbook/__init__.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,20 +2033,28 @@ def _setattr_cm(obj, **kwargs):
20332033
origs = {}
20342034
for attr in kwargs:
20352035
orig = getattr(obj, attr, sentinel)
2036-
20372036
if attr in obj.__dict__ or orig is sentinel:
2037+
# if we are pulling from the instance dict or the object
2038+
# does not have this attribute we can trust the above
20382039
origs[attr] = orig
20392040
else:
2041+
# if the attribute is not in the instance dict it must be
2042+
# from the class level
20402043
cls_orig = getattr(type(obj), attr)
2044+
# if we are dealing with a property (but not a general descriptor)
2045+
# we want to set the original value back.
20412046
if isinstance(cls_orig, property):
20422047
origs[attr] = orig
2043-
elif isinstance(cls_orig, types.FunctionType):
2044-
origs[attr] = sentinel
2048+
# otherwise this is _something_ we are going to shadow at
2049+
# the instance dict level from higher up in the MRO. We
2050+
# are going to assume we can delattr(obj, attr) to clean
2051+
# up after ourselves. It is possible that this code will
2052+
# fail if used with a non-property custom descriptor which
2053+
# implements __set__ (and __delete__ does not act like a
2054+
# stack). However, this is an internal tool and we do not
2055+
# currently have any custom descriptors.
20452056
else:
2046-
raise ValueError(
2047-
f"trying to set {attr} which is not a method, "
2048-
"property, or instance level attribute"
2049-
)
2057+
origs[attr] = sentinel
20502058

20512059
try:
20522060
for attr, val in kwargs.items():

lib/matplotlib/tests/test_cbook.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,14 @@ def __init__(self):
666666
def meth(self):
667667
...
668668

669+
@classmethod
670+
def classy(klass):
671+
...
672+
673+
@staticmethod
674+
def static():
675+
...
676+
669677
@property
670678
def prop(self):
671679
return self._p
@@ -696,6 +704,10 @@ def verify_pre_post_state(obj):
696704
assert not hasattr(obj, 'extra')
697705
assert obj.prop == 'p'
698706
assert obj.monkey == other.meth
707+
assert obj.cls_level is A.cls_level
708+
assert 'cls_level' not in obj.__dict__
709+
assert 'classy' not in obj.__dict__
710+
assert 'static' not in obj.__dict__
699711

700712
a = B()
701713

@@ -705,7 +717,8 @@ def verify_pre_post_state(obj):
705717
a, prop='squirrel',
706718
aardvark='moose', meth=lambda: None,
707719
override='boo', extra='extra',
708-
monkey=lambda: None):
720+
monkey=lambda: None, cls_level='bob',
721+
classy='classy', static='static'):
709722
# because we have set a lambda, it is normal attribute access
710723
# and the same every time
711724
assert a.meth is a.meth
@@ -715,9 +728,8 @@ def verify_pre_post_state(obj):
715728
assert a.extra == 'extra'
716729
assert a.prop == 'squirrel'
717730
assert a.monkey != other.meth
731+
assert a.cls_level == 'bob'
732+
assert a.classy == 'classy'
733+
assert a.static == 'static'
718734

719735
verify_pre_post_state(a)
720-
721-
with pytest.raises(ValueError):
722-
with cbook._setattr_cm(a, cls_level='bob'):
723-
pass

0 commit comments

Comments
 (0)