Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2706fa6

Browse files
committed
MNT: make _setattr_cm more forgiving
Our attempts to identify the set of cases we wanted to support did not correctly capture all relevant cases. This tries to simplify the logic: - if the attribute is in the instance dict, stash and restore it via setattr at the end - if the attribute is not on the object, delete it with delattr at the end - if the object has the attribute, but it is not in the instance dict: - if it is a property, stash and restore the old value - in all other cases assume that setattr will put the value in the instance dict and delattr will do what we want on the way out closes #17646
1 parent 4249e18 commit 2706fa6

2 files changed

Lines changed: 32 additions & 12 deletions

File tree

lib/matplotlib/cbook/__init__.py

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

20532061
try:
20542062
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)