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

Skip to content

Commit fb23dd1

Browse files
authored
Fix attribute setting. (#293)
- When modifying attributes on a class, PyType_Modified has to be called - The result of a lookup may be non-NULL without being a descriptor (e.g. if we set it manually using `Class.attribute = 1`)
1 parent e1b6384 commit fb23dd1

File tree

3 files changed

+51
-17
lines changed

3 files changed

+51
-17
lines changed

src/runtime/metatype.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -214,22 +214,30 @@ public static int tp_setattro(IntPtr tp, IntPtr name, IntPtr value)
214214
if (descr != IntPtr.Zero)
215215
{
216216
IntPtr dt = Runtime.PyObject_TYPE(descr);
217-
IntPtr fp = Marshal.ReadIntPtr(dt, TypeOffset.tp_descr_set);
218-
if (fp != IntPtr.Zero)
217+
218+
if (dt == Runtime.PyWrapperDescriptorType
219+
|| dt == Runtime.PyMethodType
220+
|| typeof(ExtensionType).IsInstanceOfType(GetManagedObject(descr))
221+
)
219222
{
220-
return NativeCall.Impl.Int_Call_3(fp, descr, name, value);
223+
IntPtr fp = Marshal.ReadIntPtr(dt, TypeOffset.tp_descr_set);
224+
if (fp != IntPtr.Zero)
225+
{
226+
return NativeCall.Impl.Int_Call_3(fp, descr, name, value);
227+
}
228+
else
229+
{
230+
Exceptions.SetError(Exceptions.AttributeError,
231+
"attribute is read-only");
232+
return -1;
233+
}
221234
}
222-
Exceptions.SetError(Exceptions.AttributeError,
223-
"attribute is read-only");
224-
return -1;
225235
}
226236

227-
if (Runtime.PyObject_GenericSetAttr(tp, name, value) < 0)
228-
{
229-
return -1;
230-
}
237+
var res = Runtime.PyObject_GenericSetAttr(tp, name, value);
238+
Runtime.PyType_Modified(tp);
231239

232-
return 0;
240+
return res;
233241
}
234242

235243
//====================================================================
@@ -281,7 +289,8 @@ static IntPtr DoInstanceCheck(IntPtr tp, IntPtr args, bool checkType)
281289
{
282290
ClassBase cb = GetManagedObject(tp) as ClassBase;
283291

284-
if (cb == null) {
292+
if (cb == null)
293+
{
285294
Runtime.XIncref(Runtime.PyFalse);
286295
return Runtime.PyFalse;
287296
}
@@ -298,13 +307,15 @@ static IntPtr DoInstanceCheck(IntPtr tp, IntPtr args, bool checkType)
298307
else
299308
otherType = arg.GetPythonType();
300309

301-
if (Runtime.PyObject_TYPE(otherType.Handle) != PyCLRMetaType) {
310+
if (Runtime.PyObject_TYPE(otherType.Handle) != PyCLRMetaType)
311+
{
302312
Runtime.XIncref(Runtime.PyFalse);
303313
return Runtime.PyFalse;
304314
}
305315

306316
ClassBase otherCb = GetManagedObject(otherType.Handle) as ClassBase;
307-
if (otherCb == null) {
317+
if (otherCb == null)
318+
{
308319
Runtime.XIncref(Runtime.PyFalse);
309320
return Runtime.PyFalse;
310321
}
@@ -323,4 +334,4 @@ public static IntPtr __subclasscheck__(IntPtr tp, IntPtr args)
323334
return DoInstanceCheck(tp, args, true);
324335
}
325336
}
326-
}
337+
}

src/runtime/runtime.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,16 @@ internal static void Initialize()
254254
PyMethodType = Runtime.PyObject_Type(op);
255255
Runtime.XDecref(op);
256256

257+
// For some arcane reason, builtins.__dict__.__setitem__ is *not*
258+
// a wrapper_descriptor, even though dict.__setitem__ is.
259+
//
260+
// object.__init__ seems safe, though.
261+
op = Runtime.PyObject_GetAttrString(PyBaseObjectType, "__init__");
262+
PyWrapperDescriptorType = Runtime.PyObject_Type(op);
263+
Runtime.XDecref(op);
264+
257265
#if (PYTHON32 || PYTHON33 || PYTHON34 || PYTHON35)
258266
Runtime.XDecref(dict);
259-
Runtime.XDecref(op);
260267
#endif
261268

262269
op = Runtime.PyString_FromString("string");
@@ -375,6 +382,7 @@ internal static int AtExit()
375382
internal static IntPtr PyInstanceType;
376383
internal static IntPtr PyCLRMetaType;
377384
internal static IntPtr PyMethodType;
385+
internal static IntPtr PyWrapperDescriptorType;
378386

379387
internal static IntPtr PyUnicodeType;
380388
internal static IntPtr PyStringType;
@@ -404,7 +412,6 @@ internal static int AtExit()
404412
internal static IntPtr PyNone;
405413
internal static IntPtr Error;
406414

407-
408415
internal static IntPtr GetBoundArgTuple(IntPtr obj, IntPtr args)
409416
{
410417
if (Runtime.PyObject_TYPE(args) != Runtime.PyTupleType)
@@ -2129,6 +2136,11 @@ internal static bool PyType_Check(IntPtr ob)
21292136
return PyObject_TypeCheck(ob, Runtime.PyTypeType);
21302137
}
21312138

2139+
[DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl,
2140+
ExactSpelling = true, CharSet = CharSet.Ansi)]
2141+
internal unsafe static extern void
2142+
PyType_Modified(IntPtr type);
2143+
21322144
[DllImport(Runtime.dll, CallingConvention = CallingConvention.Cdecl,
21332145
ExactSpelling = true, CharSet = CharSet.Ansi)]
21342146
internal unsafe static extern bool

src/tests/test_class.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,17 @@ def __setitem__(self, key, value):
198198

199199
self.assertTrue(table.Count == 3)
200200

201+
def testAddAndRemoveClassAttribute(self):
202+
203+
from System import TimeSpan
204+
205+
for i in range(100):
206+
TimeSpan.new_method = lambda self: self.TotalMinutes
207+
ts = TimeSpan.FromHours(1)
208+
self.assertTrue(ts.new_method() == 60)
209+
del TimeSpan.new_method
210+
self.assertFalse(hasattr(ts, "new_method"))
211+
201212

202213
class ClassicClass:
203214
def kind(self):

0 commit comments

Comments
 (0)