8000 added a few mixins to reflected .NET collection types, that implement… · pythonnet/pythonnet@5cb300a · GitHub
[go: up one dir, main page]

Skip to content

Commit 5cb300a

Browse files
committed
added a few mixins to reflected .NET collection types, that implement corresponding pythonic interfaces
1 parent 2679c19 commit 5cb300a
< 8000 /header>

File tree

8 files changed

+193
-7
lines changed

8 files changed

+193
-7
lines changed

src/runtime/InteropConfiguration.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ namespace Python.Runtime
33
using System;
44
using System.Collections.Generic;
55

6+
using Python.Runtime.Mixins;
7+
68
public sealed class InteropConfiguration
79
{
810
internal readonly PythonBaseTypeProviderGroup pythonBaseTypeProviders
@@ -18,6 +20,7 @@ public static InteropConfiguration MakeDefault()
1820
PythonBaseTypeProviders =
1921
{
2022
DefaultBaseTypeProvider.Instance,
23+
new CollectionMixinsProvider(new Lazy<PyObject>(() => Py.Import("clr._extras.collections"))),
2124
},
2225
};
2326
}
Lines changed: 90 additions & 0 deletions
< F438 tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
5+
namespace Python.Runtime.Mixins
6+
{
7+
class CollectionMixinsProvider : IPythonBaseTypeProvider
8+
{
9+
readonly Lazy<PyObject> mixinsModule;
10+
public CollectionMixinsProvider(Lazy<PyObject> mixinsModule)
11+
{
12+
this.mixinsModule = mixinsModule ?? throw new ArgumentNullException(nameof(mixinsModule));
13+
}
14+
15+
public PyObject Mixins => this.mixinsModule.Value;
16+
17+
public IEnumerable<PyType> GetBaseTypes(Type type, IList<PyType> existingBases)
18+
{
19+
if (type is null)
20+
throw new ArgumentNullException(nameof(type));
21+
22+
if (existingBases is null)
23+
throw new ArgumentNullException(nameof(existingBases));
24+
25+
var interfaces = NewInterfaces(type).Select(GetDefinition).ToArray();
26+
27+
var newBases = new List<PyType>();
28+
newBases.AddRange(existingBases);
29+
30+
// dictionaries
31+
if (interfaces.Contains(typeof(IDictionary<,>)))
32+
{
33+
newBases.Add(new PyType(this.Mixins.GetAttr("MutableMappingMixin")));
34+
}
35+
else if (interfaces.Contains(typeof(IReadOnlyDictionary<,>)))
36+
{
37+
newBases.Add(new PyType(this.Mixins.GetAttr("MappingMixin")));
38+
}
39+
40+
// item collections
41+
if (interfaces.Contains(typeof(IList<>))
42+
|| interfaces.Contains(typeof(System.Collections.IList)))
43+
{
44+
newBases.Add(new PyType(this.Mixins.GetAttr("MutableSequenceMixin")));
45+
}
46+
else if (interfaces.Contains(typeof(IReadOnlyList<>)))
47+
{
48+
newBases.Add(new PyType(this.Mixins.GetAttr("SequenceMixin")));
49+
}
50+
else if (interfaces.Contains(typeof(ICollection<>))
51+
|| interfaces.Contains(typeof(System.Collections.ICollection)))
52+
{
53+
newBases.Add(new PyType(this.Mixins.GetAttr("CollectionMixin")));
54+
}
55+
else if (interfaces.Contains(typeof(System.Collections.IEnumerable)))
56+
{
57+
newBases.Add(new PyType(this.Mixins.GetAttr("IterableMixin")));
58+
}
59+
60+
// enumerators
61+
if (interfaces.Contains(typeof(System.Collections.IEnumerator)))
62+
{
63+
newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin")));
64+
}
65+
66+
if (newBases.Count == existingBases.Count)
67+
{
68+
return existingBases;
69+
}
70+
71+
if (type.IsInterface && type.BaseType is null)
72+
{
73+
newBases.RemoveAll(@base => @base.Handle == Runtime.PyBaseObjectType);
74+
}
75+
76+
return newBases;
77+
}
78+
79+
static Type[] NewInterfaces(Type type)
80+
{
81+
var result = type.GetInterfaces();
82+
return type.BaseType != null
83+
? result.Except(type.BaseType.GetInterfaces()).ToArray()
84+
: result;
85+
}
86+
87+
static Type GetDefinition(Type type)
88+
=> type.IsGenericType ? type.GetGenericTypeDefinition() : type;
89+
}
90+
}

src/runtime/Mixins/collections.py

Lines changed: 61 additions & 0 deletions
51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Implements collections.abc for common .NET types
3+
https://docs.python.org/3.6/library/collections.abc.html
4+
"""
5+
6+
import collections.abc as col
7+
8+
class IteratorMixin(col.Iterator):
9+
def close(self):
10+
self.Dispose()
11+
12+
class IterableMixin(col.Iterable):
13+
pass
14+
15+
class SizedMixin(col.Sized):
16+
def __len__(self): return self.Count
17+
18+
class ContainerMixin(col.Container):
19+
def __contains__(self, item): return self.Contains(item)
20+
21+
try:
22+
abc_Collection = col.Collection
23+
except AttributeError:
24+
# Python 3.5- does not have collections.abc.Collection
25+
abc_Collection = col.Container
26+
27+
class CollectionMixin(SizedMixin, IterableMixin, ContainerMixin, abc_Collection):
28+
pass
29+
30+
class SequenceMixin(CollectionMixin, col.Sequence):
31+
pass
32+
33+
class MutableSequenceMixin(SequenceMixin, col.MutableSequence):
34+
pass
35+
36+
class MappingMixin(CollectionMixin, col.Mapping):
37+
def keys(self): return self.Keys
38+
def items(self): return self
39+
def values(self): return self.Values
40+
def __iter__(self): raise NotImplementedError
41+
def get(self, key):
42+
_, item = self.TryGetValue(key)
43+
return item
44+
45+
class MutableMappingMixin(MappingMixin, col.MutableMapping):
46+
def __delitem__(self, key):
47+
return self.Remove(key)
48+
def clear(self):
49+
self.Clear()
50+
def pop(self, key):
+
return self.Remove(key)
52+
def setdefault(self, key, value):
53+
existed, item = self.TryGetValue(key)
54+
if existed:
55+
return item
56+
else:
57+
self[key] = value
58+
return value
59+
def update(self, items):
60+
for key, value in items:
61+
self[key] = value

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@
3939
</ItemGroup>
4040

4141
<ItemGroup>
42-
<None Remove="resources\clr.py" />
4342
<EmbeddedResource Include="resources\clr.py">
4443
<LogicalName>clr.py</LogicalName>
4544
</EmbeddedResource>
45+
<EmbeddedResource Include="Mixins\*.py" />
4646
</ItemGroup>
4747

4848
<ItemGroup>

src/runtime/Util.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ internal static class Util
1010
internal const string MinimalPythonVersionRequired =
1111
"Only Python 3.5 or newer is supported";
1212

13+
internal const string UseOverloadWithReferenceTypes =
14+
"This API is unsafe, and will be removed in the future. Use overloads working with *Reference types";
15+
1316
internal static Int64 ReadCLong(IntPtr tp, int offset)
1417
{
1518
// On Windows, a C long is always 32 bits.

src/runtime/pyscope.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private PyScope(IntPtr ptr, PyScopeManager manager) : base(ptr)
6666
PythonException.ThrowIfIsNull(variables);
6767

6868
int res = Runtime.PyDict_SetItem(
69-
VarsRef, PyIdentifier.__builtins__,
69+
VarsRef, new BorrowedReference(PyIdentifier.__builtins__),
7070
Runtime.PyEval_GetBuiltins()
7171
);
7272
PythonException.ThrowIfIsNotZero(res);

src/runtime/pythonengine.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,8 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
223223
var locals = new PyDict();
224224
try
225225
{
226-
BorrowedReference module = Runtime.PyImport_AddModule("clr._extras");
226+
BorrowedReference module = DefineModule("clr._extras");
227227
BorrowedReference module_globals = Runtime.PyModule_GetDict(module);
228-
BorrowedReference builtins = Runtime.PyEval_GetBuiltins();
229-
Runtime.PyDict_SetItemString(module_globals, "__builtins__", builtins);
230228

231229
Assembly assembly = Assembly.GetExecutingAssembly();
232230
using (Stream stream = assembly.GetManifestResourceStream("clr.py"))
@@ -237,6 +235,8 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
237235
Exec(clr_py, module_globals, locals.Reference);
238236
}
239237

238+
LoadExtraModules(module_globals);
239+
240240
// add the imported module to the clr module, and copy the API functions
241241
// and decorators into the main clr module.
242242
Runtime.PyDict_SetItemString(clr_dict, "_extras", module);
@@ -258,6 +258,34 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
258258
}
259259
}
260260

261+
static BorrowedReference DefineModule(string name)
262+
{
263+
var module = Runtime.PyImport_AddModule(name);
264+
var module_globals = Runtime.PyModule_GetDict(module);
265+
var builtins = Runtime.PyEval_GetBuiltins();
266+
Runtime.PyDict_SetItemString(module_globals, "__builtins__", builtins);
267+
return module;
268+
}
269+
270+
static void LoadExtraModules(BorrowedReference targetModuleDict)
271+
{
272+
Assembly assembly = Assembly.GetExecutingAssembly();
273+
foreach (string nested in new[] { "collections" })
274+
{
275+
var module = DefineModule("clr._extras." + nested);
276+
var module_globals = Runtime.PyModule_GetDict(module);
277+
string resourceName = typeof(PythonEngine).Namespace + ".Mixins." + nested + ".py";
278+
using (var stream = assembly.GetManifestResourceStream(resourceName))
279+
using (var reader = new StreamReader(stream))
280+
{
281+
string pyCode = reader.ReadToEnd();
282+
Exec(pyCode, module_globals.DangerousGetAddress(), module_globals.DangerousGetAddress());
283+
}
284+
285+
Runtime.PyDict_SetItemString(targetModuleDict, nested, module);
286+
}
287+
}
288+
261289
static void OnDomainUnload(object _, EventArgs __)
262290
{
263291
Shutdown();
@@ -618,7 +646,7 @@ internal static PyObject RunString(string code, BorrowedReference globals, Borro
618646
{
619647
globals = tempGlobals = NewReference.DangerousFromPointer(Runtime.PyDict_New());
620648
Runtime.PyDict_SetItem(
621-
globals, PyIdentifier.__builtins__,
649+
globals, new BorrowedReference(PyIdentifier.__builtins__),
622650
Runtime.PyEval_GetBuiltins()
623651
);
624652
}

src/runtime/runtime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,7 @@ internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer
16791679
/// <summary>
16801680
/// Return 0 on success or -1 on failure.
16811681
/// </summary>
1682+
[Obsolete]
16821683
internal static int PyDict_SetItem(BorrowedReference dict, IntPtr key, BorrowedReference value) => Delegates.PyDict_SetItem(dict, new BorrowedReference(key), value);
16831684
/// <summary>
16841685
/// Return 0 on success or -1 on failure.
@@ -2038,7 +2039,7 @@ internal static bool PyType_IsSameAsOrSubtype(BorrowedReference type, BorrowedRe
20382039
internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases);
20392040

20402041
/// <summary>
2041-
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a types base class. Return 0 on success, or return -1 and sets an exception on error.
2042+
/// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a types base class. Return 0 on success, or return -1 and sets an exception on error.
20422043
/// </summary>
20432044

20442045
internal static int PyType_Ready(IntPtr type) => Delegates.PyType_Ready(type);

0 commit comments

Comments
 (0)
0