Skip to content

Commit 9abe140

Browse files
committed
ENH: restore class level attributes by un-shadowing in _setattr_cm
1 parent 066e6ef commit 9abe140

File tree

2 files changed

+32
-10
lines changed

2 files changed

+32
-10
lines changed

lib/matplotlib/cbook/__init__.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import types
2525
import warnings
2626
import weakref
27-
import inspect
2827

2928
import numpy as np
3029

@@ -2034,13 +2033,20 @@ def _setattr_cm(obj, **kwargs):
20342033
"""
20352034
sentinel = object()
20362035
origs = [(attr, getattr(obj, attr, sentinel)) for attr in kwargs]
2037-
# When you access a Python method the function is bound
2038-
# to the object at access time so you get a new instance
2039-
# of MethodType every time so replace them with sentinel
2040-
#
2041-
# https://docs.python.org/3/howto/descriptor.html#functions-and-methods
2042-
origs = [(attr, _ if not inspect.ismethod(_) else sentinel)
2043-
for attr, _ in origs]
2036+
2037+
# we only want to set back things that are in the instance dict or
2038+
# are properties. Everything else we are either introducing to begin with
2039+
# or are shadowing from something later in the resolution order.
2040+
def filter_restores(obj, attr, val):
2041+
if attr in obj.__dict__:
2042+
return True
2043+
2044+
if isinstance(getattr(type(obj), attr), property):
2045+
return True
2046+
return False
2047+
2048+
origs = [(attr, val if filter_restores(obj, attr, val) else sentinel)
2049+
for attr, val in origs]
20442050
try:
20452051
for attr, val in kwargs.items():
20462052
setattr(obj, attr, val)

lib/matplotlib/tests/test_cbook.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -655,8 +655,12 @@ def test_check_shape(target, test_shape):
655655

656656
def test_setattr_cm():
657657
class A:
658+
659+
cls_level = object()
660+
override = object()
658661
def __init__(self):
659662
self.aardvark = 'aardvark'
663+
self.override = 'override'
660664
self._p = 'p'
661665

662666
def meth(self):
@@ -670,7 +674,10 @@ def prop(self):
670674
def prop(self, val):
671675
self._p = val
672676

673-
a = A()
677+
class B(A):
678+
...
679+
680+
a = B()
674681
# When you access a Python method the function is bound
675682
# to the object at access time so you get a new instance
676683
# of MethodType every time.
@@ -683,19 +690,28 @@ def prop(self, val):
683690
# and our property happens to give the same instance every time
684691
assert a.prop is a.prop
685692

693+
assert a.cls_level is A.cls_level
694+
695+
assert a.override == 'override'
696+
686697
with cbook._setattr_cm(
687698
a,
688-
aardvark='moose', meth=lambda: None, prop='b'
699+
aardvark='moose', meth=lambda: None, prop='b', cls_level='bob',
700+
override='boo'
689701
):
690702
# because we have set a lambda, it is normal attribute access
691703
# and the same every time
692704
assert a.meth is a.meth
693705
assert a.aardvark is a.aardvark
694706
assert a.aardvark == 'moose'
695707
assert a.prop == 'b'
708+
assert a.cls_level == 'bob'
709+
assert a.override == 'boo'
696710

697711
# check that we get different MethodType instances each time
698712
assert a.meth is not a.meth
699713
assert a.aardvark is a.aardvark
700714
assert a.aardvark == 'aardvark'
701715
assert a.prop is a.prop
716+
assert a.cls_level is A.cls_level
717+
assert a.override == 'override'

0 commit comments

Comments
 (0)