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

Commitab2fde2

Browse files
committed
allow substituting base types for CLR types (as seen from Python)
When embedding Python, host can now provide custom implementations ofIPythonBaseTypeProvider via PythonEngine.InteropConfiguration.When .NET type is reflected to Python, this type provider will beable to specify which bases the resulting Python class will have.This implements#862
1 parent191bc89 commitab2fde2

11 files changed

+468
-49
lines changed

‎src/embed_tests/Inheritance.cs

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Diagnostics;
4+
usingSystem.Runtime.InteropServices;
5+
6+
usingNUnit.Framework;
7+
8+
usingPython.Runtime;
9+
10+
namespacePython.EmbeddingTest
11+
{
12+
publicclassInheritance
13+
{
14+
[OneTimeSetUp]
15+
publicvoidSetUp()
16+
{
17+
PythonEngine.Initialize();
18+
varlocals=newPyDict();
19+
PythonEngine.Exec(InheritanceTestBaseClassWrapper.ClassSourceCode,locals:locals.Handle);
20+
ExtraBaseTypeProvider.ExtraBase=newPyType(locals[InheritanceTestBaseClassWrapper.ClassName]);
21+
varbaseTypeProviders=PythonEngine.InteropConfiguration.PythonBaseTypeProviders;
22+
baseTypeProviders.Add(newExtraBaseTypeProvider());
23+
baseTypeProviders.Add(newNoEffectBaseTypeProvider());
24+
}
25+
26+
[OneTimeTearDown]
27+
publicvoidDispose()
28+
{
29+
PythonEngine.Shutdown();
30+
}
31+
32+
[Test]
33+
publicvoidExtraBase_PassesInstanceCheck()
34+
{
35+
varinherited=newInherited();
36+
boolproperlyInherited=PyIsInstance(inherited,ExtraBaseTypeProvider.ExtraBase);
37+
Assert.IsTrue(properlyInherited);
38+
}
39+
40+
staticdynamicPyIsInstance=>PythonEngine.Eval("isinstance");
41+
42+
[Test]
43+
publicvoidInheritingWithExtraBase_CreatesNewClass()
44+
{
45+
PyObjecta=ExtraBaseTypeProvider.ExtraBase;
46+
varinherited=newInherited();
47+
PyObjectinheritedClass=inherited.ToPython().GetAttr("__class__");
48+
Assert.IsFalse(PythonReferenceComparer.Instance.Equals(a,inheritedClass));
49+
}
50+
51+
[Test]
52+
publicvoidInheritedFromInheritedClassIsSelf()
53+
{
54+
usingvarscope=Py.CreateScope();
55+
scope.Exec($"from{typeof(Inherited).Namespace} import{nameof(Inherited)}");
56+
scope.Exec($"class B({nameof(Inherited)}): pass");
57+
PyObjectb=scope.Eval("B");
58+
PyObjectbInstance=b.Invoke();
59+
PyObjectbInstanceClass=bInstance.GetAttr("__class__");
60+
Assert.IsTrue(PythonReferenceComparer.Instance.Equals(b,bInstanceClass));
61+
}
62+
63+
[Test]
64+
publicvoidGrandchild_PassesExtraBaseInstanceCheck()
65+
{
66+
usingvarscope=Py.CreateScope();
67+
scope.Exec($"from{typeof(Inherited).Namespace} import{nameof(Inherited)}");
68+
scope.Exec($"class B({nameof(Inherited)}): pass");
69+
PyObjectb=scope.Eval("B");
70+
PyObjectbInst=b.Invoke();
71+
boolproperlyInherited=PyIsInstance(bInst,ExtraBaseTypeProvider.ExtraBase);
72+
Assert.IsTrue(properlyInherited);
73+
}
74+
75+
[Test]
76+
publicvoidCallInheritedClrMethod_WithExtraPythonBase()
77+
{
78+
varinstance=newInherited().ToPython();
79+
stringresult=instance.InvokeMethod(nameof(PythonWrapperBase.WrapperBaseMethod)).As<string>();
80+
Assert.AreEqual(result,nameof(PythonWrapperBase.WrapperBaseMethod));
81+
}
82+
83+
[Test]
84+
publicvoidCallExtraBaseMethod()
85+
{
86+
varinstance=newInherited();
87+
usingvarscope=Py.CreateScope();
88+
scope.Set(nameof(instance),instance);
89+
intactual=instance.ToPython().InvokeMethod("callVirt").As<int>();
90+
Assert.AreEqual(expected:Inherited.OverridenVirtValue,actual);
91+
}
92+
93+
[Test]
94+
publicvoidSetAdHocAttributes_WhenExtraBasePresent()
95+
{
96+
varinstance=newInherited();
97+
usingvarscope=Py.CreateScope();
98+
scope.Set(nameof(instance),instance);
99+
scope.Exec($"super({nameof(instance)}.__class__,{nameof(instance)}).set_x_to_42()");
100+
intactual=scope.Eval<int>($"{nameof(instance)}.{nameof(Inherited.XProp)}");
101+
Assert.AreEqual(expected:Inherited.X,actual);
102+
}
103+
}
104+
105+
classExtraBaseTypeProvider:IPythonBaseTypeProvider
106+
{
107+
internalstaticPyTypeExtraBase;
108+
publicIEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases)
109+
{
110+
if(type==typeof(InheritanceTestBaseClassWrapper))
111+
{
112+
returnnew[]{PyType.Get(type.BaseType),ExtraBase};
113+
}
114+
returnexistingBases;
115+
}
116+
}
117+
118+
classNoEffectBaseTypeProvider:IPythonBaseTypeProvider
119+
{
120+
publicIEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases)
121+
=>existingBases;
122+
}
123+
124+
publicclassPythonWrapperBase
125+
{
126+
publicstringWrapperBaseMethod()=>nameof(WrapperBaseMethod);
127+
}
128+
129+
publicclassInheritanceTestBaseClassWrapper:PythonWrapperBase
130+
{
131+
publicconststringClassName="InheritanceTestBaseClass";
132+
publicconststringClassSourceCode="class "+ClassName+
133+
@":
134+
def virt(self):
135+
return 42
136+
def set_x_to_42(self):
137+
self.XProp = 42
138+
def callVirt(self):
139+
return self.virt()
140+
def __getattr__(self, name):
141+
return '__getattr__:' + name
142+
def __setattr__(self, name, value):
143+
value[name] = name
144+
"+ClassName+" = "+ClassName+"\n";
145+
}
146+
147+
publicclassInherited:InheritanceTestBaseClassWrapper
148+
{
149+
publicconstintOverridenVirtValue=-42;
150+
publicconstintX=42;
151+
readonlyDictionary<string,object>extras=newDictionary<string,object>();
152+
publicintvirt()=>OverridenVirtValue;
153+
publicintXProp
154+
{
155+
get
156+
{
157+
using(varscope=Py.CreateScope())
158+
{
159+
scope.Set("this",this);
160+
try
161+
{
162+
returnscope.Eval<int>($"super(this.__class__, this).{nameof(XProp)}");
163+
}
164+
catch(PythonExceptionex)when(ex.Type.Handle==Exceptions.AttributeError)
165+
{
166+
if(this.extras.TryGetValue(nameof(this.XProp),outobjectvalue))
167+
return(int)value;
168+
throw;
169+
}
170+
}
171+
}
172+
set=>this.extras[nameof(this.XProp)]=value;
173+
}
174+
}
175+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
4+
namespacePython.Runtime
5+
{
6+
/// <summary>Minimal Python base type provider</summary>
7+
publicsealedclassDefaultBaseTypeProvider:IPythonBaseTypeProvider
8+
{
9+
publicIEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases)
10+
{
11+
if(typeisnull)
12+
thrownewArgumentNullException(nameof(type));
13+
if(existingBasesisnull)
14+
thrownewArgumentNullException(nameof(existingBases));
15+
if(existingBases.Count>0)
16+
thrownewArgumentException("To avoid confusion, this type provider requires the initial set of base types to be empty");
17+
18+
returnnew[]{newPyType(GetBaseType(type))};
19+
}
20+
21+
staticBorrowedReferenceGetBaseType(Typetype)
22+
{
23+
if(type==typeof(Exception))
24+
returnnewBorrowedReference(Exceptions.Exception);
25+
26+
returntype.BaseTypeis notnull
27+
?ClassManager.GetClass(type.BaseType).ObjectReference
28+
:newBorrowedReference(Runtime.PyBaseObjectType);
29+
}
30+
31+
DefaultBaseTypeProvider(){}
32+
publicstaticDefaultBaseTypeProviderInstance{get;}=newDefaultBaseTypeProvider();
33+
}
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
4+
namespacePython.Runtime
5+
{
6+
publicinterfaceIPythonBaseTypeProvider
7+
{
8+
/// <summary>
9+
/// Get Python types, that should be presented to Python as the base types
10+
/// for the specified .NET type.
11+
/// </summary>
12+
IEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases);
13+
}
14+
}

‎src/runtime/InteropConfiguration.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespacePython.Runtime
2+
{
3+
usingSystem;
4+
usingSystem.Collections.Generic;
5+
6+
publicsealedclassInteropConfiguration
7+
{
8+
internalreadonlyPythonBaseTypeProviderGrouppythonBaseTypeProviders
9+
=newPythonBaseTypeProviderGroup();
10+
11+
/// <summary>Enables replacing base types of CLR types as seen from Python</summary>
12+
publicIList<IPythonBaseTypeProvider>PythonBaseTypeProviders=>this.pythonBaseTypeProviders;
13+
14+
publicstaticInteropConfigurationMakeDefault()
15+
{
16+
returnnewInteropConfiguration
17+
{
18+
PythonBaseTypeProviders=
19+
{
20+
DefaultBaseTypeProvider.Instance,
21+
},
22+
};
23+
}
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Linq;
4+
5+
namespacePython.Runtime
6+
{
7+
classPythonBaseTypeProviderGroup:List<IPythonBaseTypeProvider>,IPythonBaseTypeProvider
8+
{
9+
publicIEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases)
10+
{
11+
if(typeisnull)
12+
thrownewArgumentNullException(nameof(type));
13+
if(existingBasesisnull)
14+
thrownewArgumentNullException(nameof(existingBases));
15+
16+
foreach(varproviderinthis)
17+
{
18+
existingBases=provider.GetBaseTypes(type,existingBases).ToList();
19+
}
20+
21+
returnexistingBases;
22+
}
23+
}
24+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#nullable enable
2+
usingSystem.Collections.Generic;
3+
4+
namespacePython.Runtime
5+
{
6+
/// <summary>
7+
/// Compares Python object wrappers by Python object references.
8+
/// <para>Similar to <see cref="object.ReferenceEquals"/> but for Python objects</para>
9+
/// </summary>
10+
publicsealedclassPythonReferenceComparer:IEqualityComparer<PyObject>
11+
{
12+
publicstaticPythonReferenceComparerInstance{get;}=newPythonReferenceComparer();
13+
publicboolEquals(PyObject?x,PyObject?y)
14+
{
15+
returnx?.Handle==y?.Handle;
16+
}
17+
18+
publicintGetHashCode(PyObjectobj)=>obj.Handle.GetHashCode();
19+
20+
privatePythonReferenceComparer(){}
21+
}
22+
}

‎src/runtime/StolenReference.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ public override bool Equals(object obj)
3535

3636
[Pure]
3737
publicoverrideintGetHashCode()=>Pointer.GetHashCode();
38+
39+
[Pure]
40+
publicstaticStolenReferenceDangerousFromPointer(IntPtrptr)
41+
{
42+
if(ptr==IntPtr.Zero)thrownewArgumentNullException(nameof(ptr));
43+
returnnewStolenReference(ptr);
44+
}
3845
}
3946

4047
staticclassStolenReferenceExtensions

‎src/runtime/classmanager.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,14 @@ private static ClassBase CreateClass(Type type)
252252

253253
privatestaticvoidInitClassBase(Typetype,ClassBaseimpl)
254254
{
255+
// Ensure, that matching Python type exists first.
256+
// It is required for self-referential classes
257+
// (e.g. with members, that refer to the same class)
258+
varpyType=TypeManager.GetOrCreateClass(type);
259+
260+
// Set the handle attributes on the implementing instance.
261+
impl.tpHandle=impl.pyHandle=pyType.Handle;
262+
255263
// First, we introspect the managed type and build some class
256264
// information, including generating the member descriptors
257265
// that we'll be putting in the Python class __dict__.
@@ -261,12 +269,12 @@ private static void InitClassBase(Type type, ClassBase impl)
261269
impl.indexer=info.indexer;
262270
impl.richcompare=newDictionary<int,MethodObject>();
263271

264-
// Now weallocate the Python type object to reflect the given
272+
// Now weforce initialize the Python type object to reflect the given
265273
// managed type, filling the Python type slots with thunks that
266274
// point to the managed methods providing the implementation.
267275

268276

269-
varpyType=TypeManager.GetType(impl,type);
277+
TypeManager.GetOrInitializeClass(impl,type);
270278

271279
// Finally, initialize the class __dict__ and return the object.
272280
usingvardict=Runtime.PyObject_GenericGetDict(pyType.Reference);

‎src/runtime/pythonengine.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static ShutdownMode ShutdownMode
2626
privatestaticIntPtr_pythonHome=IntPtr.Zero;
2727
privatestaticIntPtr_programName=IntPtr.Zero;
2828
privatestaticIntPtr_pythonPath=IntPtr.Zero;
29+
privatestaticInteropConfigurationinteropConfiguration=InteropConfiguration.MakeDefault();
2930

3031
publicPythonEngine()
3132
{
@@ -68,6 +69,18 @@ internal static DelegateManager DelegateManager
6869
}
6970
}
7071

72+
publicstaticInteropConfigurationInteropConfiguration
73+
{
74+
get=>interopConfiguration;
75+
set
76+
{
77+
if(IsInitialized)
78+
thrownewNotSupportedException("Changing interop configuration when engine is running is not supported");
79+
80+
interopConfiguration=value??thrownewArgumentNullException(nameof(InteropConfiguration));
81+
}
82+
}
83+
7184
publicstaticstringProgramName
7285
{
7386
get
@@ -334,6 +347,8 @@ public static void Shutdown(ShutdownMode mode)
334347
PyObjectConversions.Reset();
335348

336349
initialized=false;
350+
351+
InteropConfiguration=InteropConfiguration.MakeDefault();
337352
}
338353

339354
/// <summary>
@@ -563,7 +578,7 @@ public static ulong GetPythonThreadID()
563578
/// Interrupts the execution of a thread.
564579
/// </summary>
565580
/// <param name="pythonThreadID">The Python thread ID.</param>
566-
/// <returns>The number of thread states modified; this is normally one, but will be zero if the thread idisn’t found.</returns>
581+
/// <returns>The number of thread states modified; this is normally one, but will be zero if the thread idis not found.</returns>
567582
publicstaticintInterrupt(ulongpythonThreadID)
568583
{
569584
if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp