| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d3c6942 commit c2dff5d
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -15,8 +15,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 15 | 15 | - Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) | |
| 16 | 16 | - `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` | |
| 17 | 17 | - Improved exception handling: | |
| 18 | - - exceptions can now be converted with codecs | ||
| 19 | - - `InnerException` and `__cause__` are propagated properly | ||
| 18 | + - exceptions can now be converted with codecs | ||
| 19 | + - `InnerException` and `__cause__` are propagated properly | ||
| 20 | + - .NET arrays implement Python buffer protocol | ||
| 21 | + | ||
| 20 | 22 | ||
| 21 | 23 | ### Changed | |
| 22 | 24 | - Drop support for Python 2, 3.4, and 3.5 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,15 +2,17 @@ | |||
| 2 | 2 | using System.Collections.Generic; | |
| 3 | 3 | using NUnit.Framework; | |
| 4 | 4 | using Python.Runtime; | |
| 5 | + using Python.Runtime.Codecs; | ||
| 5 | 6 | ||
| 6 | 7 | namespace Python.EmbeddingTest | |
| 7 | 8 | { | |
| 8 | - public class TestExample | ||
| 9 | + public class NumPyTests | ||
| 9 | 10 | { | |
| 10 | 11 | [OneTimeSetUp] | |
| 11 | 12 | public void SetUp() | |
| 12 | 13 | { | |
| 13 | 14 | PythonEngine.Initialize(); | |
| 15 | + TupleCodec<ValueTuple>.Register(); | ||
| 14 | 16 | } | |
| 15 | 17 | ||
| 16 | 18 | [OneTimeTearDown] | |
@@ -49,5 +51,23 @@ public void TestReadme() | |||
| 49 | 51 | ||
| 50 | 52 | Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " ")); | |
| 51 | 53 | } | |
| 54 | + | ||
| 55 | + [Test] | ||
| 56 | + public void MultidimensionalNumPyArray() | ||
| 57 | + { | ||
| 58 | + PyObject np; | ||
| 59 | + try { | ||
| 60 | + np = Py.Import("numpy"); | ||
| 61 | + } catch (PythonException) { | ||
| 62 | + Assert.Inconclusive("Numpy or dependency not installed"); | ||
| 63 | + return; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + var array = new[,] { { 1, 2 }, { 3, 4 } }; | ||
| 67 | + var ndarray = np.InvokeMethod("asarray", array.ToPython()); | ||
| 68 | + Assert.AreEqual((2,2), ndarray.GetAttr("shape").As<(int,int)>()); | ||
| 69 | + Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As<int>()); | ||
| 70 | + Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As<int>()); | ||
| 71 | + } | ||
| 52 | 72 | } | |
| 53 | 73 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,8 @@ | |||
| 1 | + using System; | ||
| 1 | 2 | using System.Text; | |
| 2 | 3 | using NUnit.Framework; | |
| 3 | 4 | using Python.Runtime; | |
| 5 | + using Python.Runtime.Codecs; | ||
| 4 | 6 | ||
| 5 | 7 | namespace Python.EmbeddingTest { | |
| 6 | 8 | class TestPyBuffer | |
@@ -9,6 +11,7 @@ class TestPyBuffer | |||
| 9 | 11 | public void SetUp() | |
| 10 | 12 | { | |
| 11 | 13 | PythonEngine.Initialize(); | |
| 14 | + TupleCodec<ValueTuple>.Register(); | ||
| 12 | 15 | } | |
| 13 | 16 | ||
| 14 | 17 | [OneTimeTearDown] | |
@@ -64,5 +67,15 @@ public void TestBufferRead() | |||
| 64 | 67 | } | |
| 65 | 68 | } | |
| 66 | 69 | } | |
| 70 | + | ||
| 71 | + [Test] | ||
| 72 | + public void ArrayHasBuffer() | ||
| 73 | + { | ||
| 74 | + var array = new[,] {{1, 2}, {3,4}}; | ||
| 75 | + var memoryView = PythonEngine.Eval("memoryview"); | ||
| 76 | + var mem = memoryView.Invoke(array.ToPython()); | ||
| 77 | + Assert.AreEqual(1, mem[(0, 0).ToPython()].As<int>()); | ||
| 78 | + Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As<int>()); | ||
| 79 | + } | ||
| 67 | 80 | } | |
| 68 | 81 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,8 @@ internal static class Util | |||
| 10 | 10 | internal const string MinimalPythonVersionRequired = | |
| 11 | 11 | "Only Python 3.5 or newer is supported"; | |
| 12 | 12 | ||
| 13 | + internal static bool CLongIs32Bit() => Runtime.IsWindows || Runtime.Is32Bit; | ||
| 14 | + | ||
| 13 | 15 | internal static Int64 ReadCLong(IntPtr tp, int offset) | |
| 14 | 16 | { | |
| 15 | 17 | // On Windows, a C long is always 32 bits. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,7 @@ | |||
| 1 | 1 | using System; | |
| 2 | 2 | using System.Collections; | |
| 3 | + using System.Collections.Generic; | ||
| 4 | + using System.Runtime.InteropServices; | ||
| 3 | 5 | ||
| 4 | 6 | namespace Python.Runtime | |
| 5 | 7 | { | |
@@ -366,5 +368,165 @@ public static int sq_contains(IntPtr ob, IntPtr v) | |||
| 366 | 368 | ||
| 367 | 369 | return 0; | |
| 368 | 370 | } | |
| 371 | + | ||
| 372 | + #region Buffer protocol | ||
| 373 | + static int GetBuffer(BorrowedReference obj, out Py_buffer buffer, PyBUF flags) | ||
| 374 | + { | ||
| 375 | + buffer = default; | ||
| 376 | + | ||
| 377 | + if (flags == PyBUF.SIMPLE) | ||
| 378 | + { | ||
| 379 | + Exceptions.SetError(Exceptions.BufferError, "SIMPLE not implemented"); | ||
| 380 | + return -1; | ||
| 381 | + } | ||
| 382 | + if ((flags & PyBUF.F_CONTIGUOUS) == PyBUF.F_CONTIGUOUS) | ||
| 383 | + { | ||
| 384 | + Exceptions.SetError(Exceptions.BufferError, "only C-contiguous supported"); | ||
| 385 | + return -1; | ||
| 386 | + } | ||
| 387 | + var self = (Array)((CLRObject)GetManagedObject(obj)).inst; | ||
| 388 | + Type itemType = self.GetType().GetElementType(); | ||
| 389 | + | ||
| 390 | + bool formatRequested = (flags & PyBUF.FORMATS) != 0; | ||
| 391 | + string format = GetFormat(itemType); | ||
| 392 | + if (formatRequested && format is null) | ||
| 393 | + { | ||
| 394 | + Exceptions.SetError(Exceptions.BufferError, "unsupported element type: " + itemType.Name); | ||
| 395 | + return -1; | ||
| 396 | + } | ||
| 397 | + GCHandle gcHandle; | ||
| 398 | + try | ||
| 399 | + { | ||
| 400 | + gcHandle = GCHandle.Alloc(self, GCHandleType.Pinned); | ||
| 401 | + } catch (ArgumentException ex) | ||
| 402 | + { | ||
| 403 | + Exceptions.SetError(Exceptions.BufferError, ex.Message); | ||
| 404 | + return -1; | ||
| 405 | + } | ||
| 406 | + | ||
| 407 | + int itemSize = Marshal.SizeOf(itemType); | ||
| 408 | + IntPtr[] shape = GetShape(self); | ||
| 409 | + IntPtr[] strides = GetStrides(shape, itemSize); | ||
| 410 | + buffer = new Py_buffer | ||
| 411 | + { | ||
| 412 | + buf = gcHandle.AddrOfPinnedObject(), | ||
| 413 | + obj = Runtime.SelfIncRef(obj.DangerousGetAddress()), | ||
| 414 | + len = (IntPtr)(self.LongLength*itemSize), | ||
| 415 | + itemsize = (IntPtr)itemSize, | ||
| 416 | + _readonly = false, | ||
| 417 | + ndim = self.Rank, | ||
| 418 | + format = format, | ||
| 419 | + shape = ToUnmanaged(shape), | ||
| 420 | + strides = (flags & PyBUF.STRIDES) == PyBUF.STRIDES ? ToUnmanaged(strides) : IntPtr.Zero, | ||
| 421 | + suboffsets = IntPtr.Zero, | ||
| 422 | + _internal = (IntPtr)gcHandle, | ||
| 423 | + }; | ||
| 424 | + | ||
| 425 | + return 0; | ||
| 426 | + } | ||
| 427 | + static void ReleaseBuffer(BorrowedReference obj, ref Py_buffer buffer) | ||
| 428 | + { | ||
| 429 | + if (buffer._internal == IntPtr.Zero) return; | ||
| 430 | + | ||
| 431 | + UnmanagedFree(ref buffer.shape); | ||
| 432 | + UnmanagedFree(ref buffer.strides); | ||
| 433 | + UnmanagedFree(ref buffer.suboffsets); | ||
| 434 | + | ||
| 435 | + var gcHandle = (GCHandle)buffer._internal; | ||
| 436 | + gcHandle.Free(); | ||
| 437 | + buffer._internal = IntPtr.Zero; | ||
| 438 | + } | ||
| 439 | + | ||
| 440 | + static IntPtr[] GetStrides(IntPtr[] shape, long itemSize) | ||
| 441 | + { | ||
| 442 | + var result = new IntPtr[shape.Length]; | ||
| 443 | + result[shape.Length - 1] = new IntPtr(itemSize); | ||
| 444 | + for (int dim = shape.Length - 2; dim >= 0; dim--) | ||
| 445 | + { | ||
| 446 | + itemSize *= shape[dim + 1].ToInt64(); | ||
| 447 | + result[dim] = new IntPtr(itemSize); | ||
| 448 | + } | ||
| 449 | + return result; | ||
| 450 | + } | ||
| 451 | + static IntPtr[] GetShape(Array array) | ||
| 452 | + { | ||
| 453 | + var result = new IntPtr[array.Rank]; | ||
| 454 | + for (int i = 0; i < result.Length; i++) | ||
| 455 | + result[i] = (IntPtr)array.GetLongLength(i); | ||
| 456 | + return result; | ||
| 457 | + } | ||
| 458 | + | ||
| 459 | + static void UnmanagedFree(ref IntPtr address) | ||
| 460 | + { | ||
| 461 | + if (address == IntPtr.Zero) return; | ||
| 462 | + | ||
| 463 | + Marshal.FreeHGlobal(address); | ||
| 464 | + address = IntPtr.Zero; | ||
| 465 | + } | ||
| 466 | + static unsafe IntPtr ToUnmanaged<T>(T[] array) where T : unmanaged | ||
| 467 | + { | ||
| 468 | + IntPtr result = Marshal.AllocHGlobal(checked(Marshal.SizeOf(typeof(T)) * array.Length)); | ||
| 469 | + fixed (T* ptr = array) | ||
| 470 | + { | ||
| 471 | + var @out = (T*)result; | ||
| 472 | + for (int i = 0; i < array.Length; i++) | ||
| 473 | + @out[i] = ptr[i]; | ||
| 474 | + } | ||
| 475 | + return result; | ||
| 476 | + } | ||
| 477 | + | ||
| 478 | + static readonly Dictionary<Type, string> ItemFormats = new Dictionary<Type, string> | ||
| 479 | + { | ||
| 480 | + [typeof(byte)] = "B", | ||
| 481 | + [typeof(sbyte)] = "b", | ||
| 482 | + | ||
| 483 | + [typeof(bool)] = "?", | ||
| 484 | + | ||
| 485 | + [typeof(short)] = "h", | ||
| 486 | + [typeof(ushort)] = "H", | ||
| 487 | + [typeof(int)] = Util.CLongIs32Bit() ? "l" : "i", | ||
| 488 | + [typeof(uint)] = Util.CLongIs32Bit() ? "L" : "I", | ||
| 489 | + [typeof(long)] = Util.CLongIs32Bit() ? "q" : "l", | ||
| 490 | + [typeof(ulong)] = Util.CLongIs32Bit() ? "Q": "L", | ||
| 491 | + | ||
| 492 | + [typeof(IntPtr)] = "n", | ||
| 493 | + [typeof(UIntPtr)] = "N", | ||
| 494 | + | ||
| 495 | + // TODO: half = "e" | ||
| 496 | + [typeof(float)] = "f", | ||
| 497 | + [typeof(double)] = "d", | ||
| 498 | + }; | ||
| 499 | + | ||
| 500 | + static string GetFormat(Type elementType) | ||
| 501 | + => ItemFormats.TryGetValue(elementType, out string result) ? result : null; | ||
| 502 | + | ||
| 503 | + static readonly GetBufferProc getBufferProc = GetBuffer; | ||
| 504 | + static readonly ReleaseBufferProc releaseBufferProc = ReleaseBuffer; | ||
| 505 | + static readonly IntPtr BufferProcsAddress = AllocateBufferProcs(); | ||
| 506 | + static IntPtr AllocateBufferProcs() | ||
| 507 | + { | ||
| 508 | + var procs = new PyBufferProcs | ||
| 509 | + { | ||
| 510 | + Get = Marshal.GetFunctionPointerForDelegate(getBufferProc), | ||
| 511 | + Release = Marshal.GetFunctionPointerForDelegate(releaseBufferProc), | ||
| 512 | + }; | ||
| 513 | + IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PyBufferProcs))); | ||
| 514 | + Marshal.StructureToPtr(procs, result, fDeleteOld: false); | ||
| 515 | + return result; | ||
| 516 | + } | ||
| 517 | + #endregion | ||
| 518 | + | ||
| 519 | + /// <summary> | ||
| 520 | + /// <see cref="TypeManager.InitializeSlots(IntPtr, Type, SlotsHolder)"/> | ||
| 521 | + /// </summary> | ||
| 522 | + public static void InitializeSlots(IntPtr type, ISet<string> initialized, SlotsHolder slotsHolder) | ||
| 523 | + { | ||
| 524 | + if (initialized.Add(nameof(TypeOffset.tp_as_buffer))) | ||
| 525 | + { | ||
| 526 | + // TODO: only for unmanaged arrays | ||
| 527 | + int offset = TypeOffset.GetSlotOffset(nameof(TypeOffset.tp_as_buffer)); | ||
| 528 | + Marshal.WriteIntPtr(type, offset, BufferProcsAddress); | ||
| 529 | + } | ||
| 530 | + } | ||
| 369 | 531 | } | |
| 370 | 532 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -103,4 +103,15 @@ public enum PyBUF | |||
| 103 | 103 | /// </summary> | |
| 104 | 104 | FULL_RO = (INDIRECT | FORMATS), | |
| 105 | 105 | } | |
| 106 | + | ||
| 107 | + internal struct PyBufferProcs | ||
| 108 | + { | ||
| 109 | + public IntPtr Get; | ||
| 110 | + public IntPtr Release; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] | ||
| 114 | + delegate int GetBufferProc(BorrowedReference obj, out Py_buffer buffer, PyBUF flags); | ||
| 115 | + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] | ||
| 116 | + delegate void ReleaseBufferProc(BorrowedReference obj, ref Py_buffer buffer); | ||
| 106 | 117 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -413,6 +413,7 @@ public static variables on the Exceptions class filled in from | |||
| 413 | 413 | ||
| 414 | 414 | public static IntPtr AssertionError; | |
| 415 | 415 | public static IntPtr AttributeError; | |
| 416 | + public static IntPtr BufferError; | ||
| 416 | 417 | public static IntPtr EOFError; | |
| 417 | 418 | public static IntPtr FloatingPointError; | |
| 418 | 419 | public static IntPtr EnvironmentError; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -159,6 +159,7 @@ static void ValidateRequiredOffsetsPresent(PropertyInfo[] offsetProperties) | |||
| 159 | 159 | "GetClrType", | |
| 160 | 160 | "getPreload", | |
| 161 | 161 | "Initialize", | |
| 162 | + "InitializeSlots", | ||
| 162 | 163 | "ListAssemblies", | |
| 163 | 164 | "_load_clr_module", | |
| 164 | 165 | "Release", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -691,6 +691,9 @@ internal static void InitializeSlots(IntPtr type, Type impl, SlotsHolder slotsHo | |||
| 691 | 691 | seen.Add(name); | |
| 692 | 692 | } | |
| 693 | 693 | ||
| 694 | + var initSlot = impl.GetMethod("InitializeSlots", BindingFlags.Static | BindingFlags.Public); | ||
| 695 | + initSlot?.Invoke(null, parameters: new object[] { type, seen, slotsHolder }); | ||
| 696 | + | ||
| 694 | 697 | impl = impl.BaseType; | |
| 695 | 698 | } | |
| 696 | 699 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments