Skip to content

Commit 066e6ef

Browse files
committed
MNT: don't set method objects back onto the objects
If we are monkey-patching methods then we need to be sure that when we restore the original attribute we need to make sure we don't stick the method instance in place (which creates a circular reference).
1 parent 4356b67 commit 066e6ef

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

lib/matplotlib/cbook/__init__.py

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

2829
import numpy as np
2930

@@ -2033,6 +2034,13 @@ def _setattr_cm(obj, **kwargs):
20332034
"""
20342035
sentinel = object()
20352036
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]
20362044
try:
20372045
for attr, val in kwargs.items():
20382046
setattr(obj, attr, val)

lib/matplotlib/tests/test_cbook.py

+48
Original file line numberDiff line numberDiff line change
@@ -651,3 +651,51 @@ def test_check_shape(target, test_shape):
651651
with pytest.raises(ValueError,
652652
match=error_pattern):
653653
cbook._check_shape(target, aardvark=data)
654+
655+
656+
def test_setattr_cm():
657+
class A:
658+
def __init__(self):
659+
self.aardvark = 'aardvark'
660+
self._p = 'p'
661+
662+
def meth(self):
663+
...
664+
665+
@property
666+
def prop(self):
667+
return self._p
668+
669+
@prop.setter
670+
def prop(self, val):
671+
self._p = val
672+
673+
a = A()
674+
# When you access a Python method the function is bound
675+
# to the object at access time so you get a new instance
676+
# of MethodType every time.
677+
#
678+
# https://docs.python.org/3/howto/descriptor.html#functions-and-methods
679+
assert a.meth is not a.meth
680+
# normal attribute should give you back the same
681+
# instance every time
682+
assert a.aardvark is a.aardvark
683+
# and our property happens to give the same instance every time
684+
assert a.prop is a.prop
685+
686+
with cbook._setattr_cm(
687+
a,
688+
aardvark='moose', meth=lambda: None, prop='b'
689+
):
690+
# because we have set a lambda, it is normal attribute access
691+
# and the same every time
692+
assert a.meth is a.meth
693+
assert a.aardvark is a.aardvark
694+
assert a.aardvark == 'moose'
695+
assert a.prop == 'b'
696+
697+
# check that we get different MethodType instances each time
698+
assert a.meth is not a.meth
699+
assert a.aardvark is a.aardvark
700+
assert a.aardvark == 'aardvark'
701+
assert a.prop is a.prop

0 commit comments

Comments
 (0)