Skip to content

Commit 1d52e3f

Browse files
authored
Replace setattr and getattr calls with __dict__ manipulations (#905)
- fixes problems with custom setattr__ implementations like pytorch
1 parent c4444e4 commit 1d52e3f

File tree

2 files changed

+16
-22
lines changed

2 files changed

+16
-22
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The released versions correspond to PyPI releases.
66
### Fixes
77
* fixes the problem that filesystem patching was still active in the pytest
88
logreport phase (see [#904](../../issues/904))
9+
* Restores compatibility with PyTorch 2.0 and above, as well as with other
10+
classes that have custom __setattr__ methods (see [#905](../../pull/905)).
911

1012
## [Version 5.3.0](https://pypi.python.org/pypi/pyfakefs/5.3.0) (2023-10-11)
1113
Adds official support for Python 3.12.

pyfakefs/mox3_stubout.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,20 @@ def smart_set(self, obj, attr_name, new_attr):
6161
This method supports the case where attr_name is a staticmethod or a
6262
classmethod of obj.
6363
64-
Notes:
65-
- If obj is an instance, then it is its class that will actually be
66-
stubbed. Note that the method Set() does not do that: if obj is
67-
an instance, it (and not its class) will be stubbed.
68-
- The stubbing is using the builtin getattr and setattr. So, the
69-
__get__ and __set__ will be called when stubbing (TODO: A better
70-
idea would probably be to manipulate obj.__dict__ instead of
71-
getattr() and setattr()).
64+
If obj is an instance, then it is its class that will actually be
65+
stubbed. Note that the method Set() does not do that: if obj is an
66+
instance, it (and not its class) will be stubbed.
7267
7368
Raises AttributeError if the attribute cannot be found.
7469
"""
7570
if inspect.ismodule(obj) or (
7671
not inspect.isclass(obj) and attr_name in obj.__dict__
7772
):
7873
orig_obj = obj
79-
orig_attr = getattr(obj, attr_name)
74+
if attr_name in obj.__dict__:
75+
orig_attr = obj.__dict__[attr_name]
76+
else:
77+
orig_attr = None
8078

8179
else:
8280
if not inspect.isclass(obj):
@@ -91,21 +89,15 @@ def smart_set(self, obj, attr_name, new_attr):
9189
for cls in mro:
9290
try:
9391
orig_obj = cls
94-
orig_attr = getattr(obj, attr_name)
95-
except AttributeError:
92+
orig_attr = obj.__dict__[attr_name]
93+
except KeyError:
9694
continue
9795

9896
if orig_attr is None:
9997
raise AttributeError("Attribute not found.")
10098

101-
# Calling getattr() on a staticmethod transforms it to a 'normal'
102-
# function. We need to ensure that we put it back as a staticmethod.
103-
old_attribute = obj.__dict__.get(attr_name)
104-
if old_attribute is not None and isinstance(old_attribute, staticmethod):
105-
orig_attr = staticmethod(orig_attr) # pytype: disable=not-callable
106-
10799
self.stubs.append((orig_obj, attr_name, orig_attr))
108-
setattr(orig_obj, attr_name, new_attr)
100+
orig_obj.__dict__[attr_name] = new_attr
109101

110102
def smart_unset_all(self):
111103
"""Reverses all the SmartSet() calls.
@@ -116,8 +108,8 @@ def smart_unset_all(self):
116108
"""
117109
self.stubs.reverse()
118110

119-
for args in self.stubs:
120-
setattr(*args)
111+
for obj, attr_name, old_attr in self.stubs:
112+
obj.__dict__[attr_name] = old_attr
121113

122114
self.stubs = []
123115

@@ -143,7 +135,7 @@ def set(self, parent, child_name, new_child):
143135
old_child = classmethod(old_child.__func__)
144136

145137
self.cache.append((parent, old_child, child_name))
146-
setattr(parent, child_name, new_child)
138+
parent.__dict__[child_name] = new_child
147139

148140
def unset_all(self):
149141
"""Reverses all the Set() calls.
@@ -158,5 +150,5 @@ def unset_all(self):
158150
self.cache.reverse()
159151

160152
for parent, old_child, child_name in self.cache:
161-
setattr(parent, child_name, old_child)
153+
parent.__dict__[child_name] = old_child
162154
self.cache = []

0 commit comments

Comments
 (0)