Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3b4da04

Browse files
committed
MNT: make _setattr_cm more conservative
- special case methods and properties because they are descriptors - fail on any other non-instance attribute
1 parent21c3acb commit3b4da04

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

‎lib/matplotlib/cbook/__init__.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,14 +2031,35 @@ def _setattr_cm(obj, **kwargs):
20312031
"""
20322032
Temporarily set some attributes; restore original state at context exit.
20332033
"""
2034+
importinspect
20342035
sentinel=object()
2035-
origs= [(attr,getattr(obj,attr,sentinel))forattrinkwargs]
2036+
origs= {}
2037+
forattrinkwargs:
2038+
orig=getattr(obj,attr,sentinel)
2039+
# monkey patching on a new attribute, this is OK
2040+
iforigissentinel:
2041+
origs[attr]=sentinel
2042+
# special case methods
2043+
elifinspect.ismethod(orig):
2044+
origs[attr]=sentinel
2045+
else:
2046+
# if we are trying to monkey patch a non-instance attribute,
2047+
# fail so we don't have to sort out how to safely identify
2048+
# general descriptors.
2049+
ifattrnotinobj.__dict__:
2050+
ifnotisinstance(getattr(type(obj),attr),property):
2051+
raiseValueError(
2052+
f"trying to set{attr} which is not a method, "
2053+
"property, or instance level attribute"
2054+
)
2055+
origs[attr]=orig
2056+
20362057
try:
20372058
forattr,valinkwargs.items():
20382059
setattr(obj,attr,val)
20392060
yield
20402061
finally:
2041-
forattr,originorigs:
2062+
forattr,originorigs.items():
20422063
iforigissentinel:
20432064
delattr(obj,attr)
20442065
else:

‎lib/matplotlib/tests/test_cbook.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,3 +651,68 @@ def test_check_shape(target, test_shape):
651651
withpytest.raises(ValueError,
652652
match=error_pattern):
653653
cbook._check_shape(target,aardvark=data)
654+
655+
656+
deftest_setattr_cm():
657+
classA:
658+
659+
cls_level=object()
660+
override=object()
661+
def__init__(self):
662+
self.aardvark='aardvark'
663+
self.override='override'
664+
self._p='p'
665+
666+
defmeth(self):
667+
...
668+
669+
@property
670+
defprop(self):
671+
returnself._p
672+
673+
@prop.setter
674+
defprop(self,val):
675+
self._p=val
676+
677+
classB(A):
678+
...
679+
680+
defverify_pre_post_state(obj):
681+
# When you access a Python method the function is bound
682+
# to the object at access time so you get a new instance
683+
# of MethodType every time.
684+
#
685+
# https://docs.python.org/3/howto/descriptor.html#functions-and-methods
686+
assertobj.methisnotobj.meth
687+
# normal attribute should give you back the same
688+
# instance every time
689+
assertobj.aardvarkisobj.aardvark
690+
asserta.aardvark=='aardvark'
691+
# and our property happens to give the same instance every time
692+
assertobj.propisobj.prop
693+
assertobj.cls_levelisA.cls_level
694+
assertobj.override=='override'
695+
assertnothasattr(obj,'extra')
696+
assertobj.prop=='p'
697+
698+
a=B()
699+
verify_pre_post_state(a)
700+
withcbook._setattr_cm(
701+
a,prop='squirrel',
702+
aardvark='moose',meth=lambda:None,
703+
override='boo',extra='extra'
704+
):
705+
# because we have set a lambda, it is normal attribute access
706+
# and the same every time
707+
asserta.methisa.meth
708+
asserta.aardvarkisa.aardvark
709+
asserta.aardvark=='moose'
710+
asserta.override=='boo'
711+
asserta.extra=='extra'
712+
asserta.prop=='squirrel'
713+
714+
verify_pre_post_state(a)
715+
716+
withpytest.raises(ValueError):
717+
withcbook._setattr_cm(a,cls_level='bob'):
718+
...

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp