forked from pythonnet/pythonnet
-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathDynamicClassObject.cs
More file actions
145 lines (129 loc) · 6.17 KB
/
DynamicClassObject.cs
File metadata and controls
145 lines (129 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using RuntimeBinder = Microsoft.CSharp.RuntimeBinder;
namespace Python.Runtime
{
/// <summary>
/// Managed class that provides the implementation for reflected dynamic types.
/// This has the usage as ClassObject but for the dynamic types special case,
/// that is, classes implementing IDynamicMetaObjectProvider interface.
/// This adds support for using dynamic properties of the C# object.
/// </summary>
[Serializable]
internal class DynamicClassObject : ClassObject
{
internal DynamicClassObject(Type tp) : base(tp)
{
}
private static Dictionary<ValueTuple<Type, string>, CallSite<Func<CallSite, object, object>>> _getAttrCallSites = new();
private static Dictionary<ValueTuple<Type, string>, CallSite<Func<CallSite, object, object, object>>> _setAttrCallSites = new();
private static CallSite<Func<CallSite, object, object>> GetAttrCallSite(string name, Type objectType)
{
var key = ValueTuple.Create(objectType, name);
if (!_getAttrCallSites.TryGetValue(key, out var callSite))
{
var binder = RuntimeBinder.Binder.GetMember(
RuntimeBinder.CSharpBinderFlags.None,
name,
objectType,
new[] { RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null) });
callSite = CallSite<Func<CallSite, object, object>>.Create(binder);
_getAttrCallSites[key] = callSite;
}
return callSite;
}
private static CallSite<Func<CallSite, object, object, object>> SetAttrCallSite(string name, Type objectType)
{
var key = ValueTuple.Create(objectType, name);
if (!_setAttrCallSites.TryGetValue(key, out var callSite))
{
var binder = RuntimeBinder.Binder.SetMember(
RuntimeBinder.CSharpBinderFlags.None,
name,
objectType,
new[]
{
RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null),
RuntimeBinder.CSharpArgumentInfo.Create(RuntimeBinder.CSharpArgumentInfoFlags.None, null)
});
callSite = CallSite<Func<CallSite, object, object, object>>.Create(binder);
_setAttrCallSites[key] = callSite;
}
return callSite;
}
/// <summary>
/// Type __getattro__ implementation.
/// </summary>
public static NewReference tp_getattro(BorrowedReference ob, BorrowedReference key)
{
if (!TryGetNonDynamicMember(ob, key, out var result))
{
var clrObj = (CLRObject)GetManagedObject(ob)!;
var name = Runtime.GetManagedString(key);
var clrObjectType = clrObj.inst.GetType();
var callSite = GetAttrCallSite(name, clrObjectType);
try
{
var res = callSite.Target(callSite, clrObj.inst);
Exceptions.Clear();
result = Converter.ToPython(res);
}
catch (RuntimeBinder.RuntimeBinderException)
{
// Do nothing, AttributeError was already raised in Python side and it was not cleared.
}
// Catch C# exceptions and raise them as Python exceptions.
catch (Exception exception)
{
Exceptions.Clear();
// tp_getattro should call PyObject_GenericGetAttr (which we already did)
// which must throw AttributeError if the attribute is not found (see https://docs.python.org/3/c-api/object.html#c.PyObject_GenericGetAttr)
// So if we are throwing anything, it must be AttributeError.
// e.g hasattr uses this method to check if the attribute exists. If we throw anything other than AttributeError,
// hasattr will throw instead of catching and returning False.
Exceptions.SetError(Exceptions.AttributeError, exception.Message);
return default;
}
}
return result;
}
/// <summary>
/// Type __setattr__ implementation.
/// </summary>
public static int tp_setattro(BorrowedReference ob, BorrowedReference key, BorrowedReference val)
{
if (TryGetNonDynamicMember(ob, key, out _, clearExceptions: true))
{
return Runtime.PyObject_GenericSetAttr(ob, key, val);
}
var clrObj = (CLRObject)GetManagedObject(ob)!;
var name = Runtime.GetManagedString(key);
var callsite = SetAttrCallSite(name, clrObj.inst.GetType());
try
{
callsite.Target(callsite, clrObj.inst, PyObject.FromNullableReference(val));
}
// Catch C# exceptions and raise them as Python exceptions.
catch (Exception exception)
{
// tp_setattro should call PyObject_GenericSetAttr (which we already did)
// which must throw AttributeError on failure and return -1 (see https://docs.python.org/3/c-api/object.html#c.PyObject_GenericSetAttr)
Exceptions.SetError(Exceptions.AttributeError, exception.Message);
return -1;
}
return 0;
}
private static bool TryGetNonDynamicMember(BorrowedReference ob, BorrowedReference key, out NewReference value, bool clearExceptions = false)
{
value = Runtime.PyObject_GenericGetAttr(ob, key);
// If AttributeError was raised, we try to get the attribute from the managed object dynamic properties.
var result = !Exceptions.ExceptionMatches(Exceptions.AttributeError);
if (clearExceptions)
{
Exceptions.Clear();
}
return result;
}
}
}