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

Commit12defa7

Browse files
authored
Merge pull request#1543 from losttech/standard-mixins
Mixins for standard .NET collections that implement `collections.abc` interfaces
2 parents2b0e322 +6ae373c commit12defa7

14 files changed

+249
-12
lines changed

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1717
- Improved exception handling:
1818
- exceptions can now be converted with codecs
1919
-`InnerException` and`__cause__` are propagated properly
20+
- .NET collection types now implement standard Python collection interfaces from`collections.abc`.
21+
See[Mixins/collections.py](src/runtime/Mixins/collections.py).
2022
- .NET arrays implement Python buffer protocol
2123

2224

‎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
usingSystem;
44
usingSystem.Collections.Generic;
55

6+
usingPython.Runtime.Mixins;
7+
68
publicsealedclassInteropConfiguration
79
{
810
internalreadonlyPythonBaseTypeProviderGrouppythonBaseTypeProviders
@@ -18,6 +20,7 @@ public static InteropConfiguration MakeDefault()
1820
PythonBaseTypeProviders=
1921
{
2022
DefaultBaseTypeProvider.Instance,
23+
newCollectionMixinsProvider(newLazy<PyObject>(()=>Py.Import("clr._extras.collections"))),
2124
},
2225
};
2326
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
usingSystem;
2+
usingSystem.Collections.Generic;
3+
usingSystem.Linq;
4+
5+
namespacePython.Runtime.Mixins
6+
{
7+
classCollectionMixinsProvider:IPythonBaseTypeProvider
8+
{
9+
readonlyLazy<PyObject>mixinsModule;
10+
publicCollectionMixinsProvider(Lazy<PyObject>mixinsModule)
11+
{
12+
this.mixinsModule=mixinsModule??thrownewArgumentNullException(nameof(mixinsModule));
13+
}
14+
15+
publicPyObjectMixins=>this.mixinsModule.Value;
16+
17+
publicIEnumerable<PyType>GetBaseTypes(Typetype,IList<PyType>existingBases)
18+
{
19+
if(typeisnull)
20+
thrownewArgumentNullException(nameof(type));
21+
22+
if(existingBasesisnull)
23+
thrownewArgumentNullException(nameof(existingBases));
24+
25+
varinterfaces=NewInterfaces(type).Select(GetDefinition).ToArray();
26+
27+
varnewBases=newList<PyType>();
28+
newBases.AddRange(existingBases);
29+
30+
// dictionaries
31+
if(interfaces.Contains(typeof(IDictionary<,>)))
32+
{
33+
newBases.Add(newPyType(this.Mixins.GetAttr("MutableMappingMixin")));
34+
}
35+
elseif(interfaces.Contains(typeof(IReadOnlyDictionary<,>)))
36+
{
37+
newBases.Add(newPyType(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(newPyType(this.Mixins.GetAttr("MutableSequenceMixin")));
45+
}
46+
elseif(interfaces.Contains(typeof(IReadOnlyList<>)))
47+
{
48+
newBases.Add(newPyType(this.Mixins.GetAttr("SequenceMixin")));
49+
}
50+
elseif(interfaces.Contains(typeof(ICollection<>))
51+
||interfaces.Contains(typeof(System.Collections.ICollection)))
52+
{
53+
newBases.Add(newPyType(this.Mixins.GetAttr("CollectionMixin")));
54+
}
55+
elseif(interfaces.Contains(typeof(System.Collections.IEnumerable)))
56+
{
57+
newBases.Add(newPyType(this.Mixins.GetAttr("IterableMixin")));
58+
}
59+
60+
// enumerators
61+
if(interfaces.Contains(typeof(System.Collections.IEnumerator)))
62+
{
63+
newBases.Add(newPyType(this.Mixins.GetAttr("IteratorMixin")));
64+
}
65+
66+
if(newBases.Count==existingBases.Count)
67+
{
68+
returnexistingBases;
69+
}
70+
71+
if(type.IsInterface&&type.BaseTypeisnull)
72+
{
73+
newBases.RemoveAll(@base=>@base.Handle==Runtime.PyBaseObjectType);
74+
}
75+
76+
returnnewBases;
77+
}
78+
79+
staticType[]NewInterfaces(Typetype)
80+
{
81+
varresult=type.GetInterfaces();
82+
returntype.BaseType!=null
83+
?result.Except(type.BaseType.GetInterfaces()).ToArray()
84+
:result;
85+
}
86+
87+
staticTypeGetDefinition(Typetype)
88+
=>type.IsGenericType?type.GetGenericTypeDefinition():type;
89+
}
90+
}

‎src/runtime/Mixins/collections.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
"""
2+
Implements collections.abc for common .NET types
3+
https://docs.python.org/3.6/library/collections.abc.html
4+
"""
5+
6+
importcollections.abcascol
7+
8+
classIteratorMixin(col.Iterator):
9+
defclose(self):
10+
self.Dispose()
11+
12+
classIterableMixin(col.Iterable):
13+
pass
14+
15+
classSizedMixin(col.Sized):
16+
def__len__(self):returnself.Count
17+
18+
classContainerMixin(col.Container):
19+
def__contains__(self,item):returnself.Contains(item)
20+
21+
try:
22+
abc_Collection=col.Collection
23+
exceptAttributeError:
24+
# Python 3.5- does not have collections.abc.Collection
25+
abc_Collection=col.Container
26+
27+
classCollectionMixin(SizedMixin,IterableMixin,ContainerMixin,abc_Collection):
28+
pass
29+
30+
classSequenceMixin(CollectionMixin,col.Sequence):
31+
pass
32+
33+
classMutableSequenceMixin(SequenceMixin,col.MutableSequence):
34+
pass
35+
36+
classMappingMixin(CollectionMixin,col.Mapping):
37+
def__contains__(self,item):returnself.ContainsKey(item)
38+
defkeys(self):returnself.Keys
39+
defitems(self):return [(k,self.get(k))forkinself.Keys]
40+
defvalues(self):returnself.Values
41+
def__iter__(self):returnself.Keys.__iter__()
42+
defget(self,key,default=None):
43+
existed,item=self.TryGetValue(key,None)
44+
returnitemifexistedelsedefault
45+
46+
classMutableMappingMixin(MappingMixin,col.MutableMapping):
47+
_UNSET_=object()
48+
49+
def__delitem__(self,key):
50+
self.Remove(key)
51+
52+
defclear(self):
53+
self.Clear()
54+
55+
defpop(self,key,default=_UNSET_):
56+
existed,item=self.TryGetValue(key,None)
57+
ifexisted:
58+
self.Remove(key)
59+
returnitem
60+
elifdefault==self._UNSET_:
61+
raiseKeyError(key)
62+
else:
63+
returndefault
64+
65+
defsetdefault(self,key,value=None):
66+
existed,item=self.TryGetValue(key,None)
67+
ifexisted:
68+
returnitem
69+
else:
70+
self[key]=value
71+
returnvalue
72+
73+
defupdate(self,items,**kwargs):
74+
ifisinstance(items,col.Mapping):
75+
forkey,valueinitems.items():
76+
self[key]=value
77+
else:
78+
forkey,valueinitems:
79+
self[key]=value
80+
81+
forkey,valueinkwargs.items():
82+
self[key]=value

‎src/runtime/Python.Runtime.csproj

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

4141
<ItemGroup>
42-
<NoneRemove="resources\clr.py" />
4342
<EmbeddedResourceInclude="resources\clr.py">
4443
<LogicalName>clr.py</LogicalName>
4544
</EmbeddedResource>
4645
<EmbeddedResourceInclude="resources\interop.py">
4746
<LogicalName>interop.py</LogicalName>
4847
</EmbeddedResource>
48+
<EmbeddedResourceInclude="Mixins\*.py" />
4949
</ItemGroup>
5050

5151
<ItemGroup>

‎src/runtime/Util.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ internal static class Util
1212
internalconststringMinimalPythonVersionRequired=
1313
"Only Python 3.5 or newer is supported";
1414

15+
internalconststringUseOverloadWithReferenceTypes=
16+
"This API is unsafe, and will be removed in the future. Use overloads working with *Reference types";
17+
1518
internalstaticInt64ReadCLong(IntPtrtp,intoffset)
1619
{
1720
// On Windows, a C long is always 32 bits.

‎src/runtime/classbase.cs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,18 +360,40 @@ public static void tp_dealloc(IntPtr ob)
360360

361361
publicstaticinttp_clear(IntPtrob)
362362
{
363-
ManagedTypeself=GetManagedObject(ob);
363+
if(GetManagedObject(ob)is{}self)
364+
{
365+
if(self.clearReentryGuard)return0;
366+
367+
// workaround for https://bugs.python.org/issue45266
368+
self.clearReentryGuard=true;
369+
370+
try
371+
{
372+
returnClearImpl(ob,self);
373+
}
374+
finally
375+
{
376+
self.clearReentryGuard=false;
377+
}
378+
}
379+
else
380+
{
381+
returnClearImpl(ob,null);
382+
}
383+
}
364384

385+
staticintClearImpl(IntPtrob,ManagedTypeself)
386+
{
365387
boolisTypeObject=Runtime.PyObject_TYPE(ob)==Runtime.PyCLRMetaType;
366388
if(!isTypeObject)
367389
{
368-
ClearObjectDict(ob);
369-
370390
intbaseClearResult=BaseUnmanagedClear(ob);
371391
if(baseClearResult!=0)
372392
{
373393
returnbaseClearResult;
374394
}
395+
396+
ClearObjectDict(ob);
375397
}
376398
return0;
377399
}

‎src/runtime/clrobject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class CLRObject : ManagedType
1212

1313
internalCLRObject(objectob,IntPtrtp)
1414
{
15-
System.Diagnostics.Debug.Assert(tp!=IntPtr.Zero);
15+
Debug.Assert(tp!=IntPtr.Zero);
1616
IntPtrpy=Runtime.PyType_GenericAlloc(tp,0);
1717

1818
tpHandle=tp;

‎src/runtime/managedtype.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ internal enum TrackTypes
2828
internalIntPtrpyHandle;// PyObject *
2929
internalIntPtrtpHandle;// PyType *
3030

31+
internalboolclearReentryGuard;
32+
3133
internalBorrowedReferenceObjectReference
3234
{
3335
get
@@ -160,7 +162,7 @@ internal static bool IsInstanceOfManagedType(IntPtr ob)
160162

161163
internalstaticboolIsManagedType(BorrowedReferencetype)
162164
{
163-
varflags=(TypeFlags)Util.ReadCLong(type.DangerousGetAddress(),TypeOffset.tp_flags);
165+
varflags=PyType.GetFlags(type);
164166
return(flags&TypeFlags.HasClrInstance)!=0;
165167
}
166168

‎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
intres=Runtime.PyDict_SetItem(
69-
VarsRef,PyIdentifier.__builtins__,
69+
VarsRef,newBorrowedReference(PyIdentifier.__builtins__),
7070
Runtime.PyEval_GetBuiltins()
7171
);
7272
PythonException.ThrowIfIsNotZero(res);

‎src/runtime/pythonengine.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,8 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
224224
varlocals=newPyDict();
225225
try
226226
{
227-
BorrowedReferencemodule=Runtime.PyImport_AddModule("clr._extras");
227+
BorrowedReferencemodule=DefineModule("clr._extras");
228228
BorrowedReferencemodule_globals=Runtime.PyModule_GetDict(module);
229-
BorrowedReferencebuiltins=Runtime.PyEval_GetBuiltins();
230-
Runtime.PyDict_SetItemString(module_globals,"__builtins__",builtins);
231229

232230
Assemblyassembly=Assembly.GetExecutingAssembly();
233231
// add the contents of clr.py to the module
@@ -236,6 +234,8 @@ public static void Initialize(IEnumerable<string> args, bool setSysArgv = true,
236234

237235
LoadSubmodule(module_globals,"clr.interop","interop.py");
238236

237+
LoadMixins(module_globals);
238+
239239
// add the imported module to the clr module, and copy the API functions
240240
// and decorators into the main clr module.
241241
Runtime.PyDict_SetItemString(clr_dict,"_extras",module);
@@ -281,6 +281,16 @@ static void LoadSubmodule(BorrowedReference targetModuleDict, string fullName, s
281281
Runtime.PyDict_SetItemString(targetModuleDict,memberName,module);
282282
}
283283

284+
staticvoidLoadMixins(BorrowedReferencetargetModuleDict)
285+
{
286+
foreach(stringnestedinnew[]{"collections"})
287+
{
288+
LoadSubmodule(targetModuleDict,
289+
fullName:"clr._extras."+nested,
290+
resourceName:typeof(PythonEngine).Namespace+".Mixins."+nested+".py");
291+
}
292+
}
293+
284294
staticvoidOnDomainUnload(object_,EventArgs__)
285295
{
286296
Shutdown();
@@ -641,7 +651,7 @@ internal static PyObject RunString(string code, BorrowedReference globals, Borro
641651
{
642652
globals=tempGlobals=NewReference.DangerousFromPointer(Runtime.PyDict_New());
643653
Runtime.PyDict_SetItem(
644-
globals,PyIdentifier.__builtins__,
654+
globals,newBorrowedReference(PyIdentifier.__builtins__),
645655
Runtime.PyEval_GetBuiltins()
646656
);
647657
}

‎src/runtime/pytype.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ internal IntPtr GetSlot(TypeSlotID slot)
103103
returnExceptions.ErrorCheckIfNull(result);
104104
}
105105

106+
internalstaticTypeFlagsGetFlags(BorrowedReferencetype)
107+
{
108+
Debug.Assert(TypeOffset.tp_flags>0);
109+
return(TypeFlags)Util.ReadCLong(type.DangerousGetAddress(),TypeOffset.tp_flags);
110+
}
111+
106112
internalstaticBorrowedReferenceGetBase(BorrowedReferencetype)
107113
{
108114
Debug.Assert(IsType(type));

‎src/runtime/runtime.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,7 @@ internal static BorrowedReference PyDict_GetItemString(BorrowedReference pointer
16891689
/// <summary>
16901690
/// Return 0 on success or -1 on failure.
16911691
/// </summary>
1692+
[Obsolete]
16921693
internal static int PyDict_SetItem(BorrowedReference dict, IntPtr key, BorrowedReference value) => Delegates.PyDict_SetItem(dict, new BorrowedReference(key), value);
16931694
/// <summary>
16941695
/// Return 0 on success or -1 on failure.
@@ -2052,7 +2053,7 @@ internal static bool PyType_IsSameAsOrSubtype(BorrowedReference type, BorrowedRe
20522053
internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases);
20532054

20542055
/// <summary>
2055-
/// 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.
2056+
/// 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.
20562057
/// </summary>
20572058

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

‎tests/test_collection_mixins.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
importSystem.Collections.GenericasC
2+
3+
deftest_contains():
4+
l=C.List[int]()
5+
l.Add(42)
6+
assert42inl
7+
assert43notinl
8+
9+
deftest_dict_items():
10+
d=C.Dictionary[int,str]()
11+
d[42]="a"
12+
items=d.items()
13+
assertlen(items)==1
14+
k,v=items[0]
15+
assertk==42
16+
assertv=="a"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp