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

Implements buffer interface for .NET arrays of primitive types#1511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
filmor merged 1 commit intopythonnet:masterfromlosttech:array-buffer-interface
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions.github/workflows/main.yml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,6 +41,7 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade -r requirements.txt
pip install numpy # for tests

- name: Build and Install
run: |
Expand Down
6 changes: 4 additions & 2 deletionsCHANGELOG.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,8 +15,10 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
- Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355])
- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec`
- Improved exception handling:
- exceptions can now be converted with codecs
- `InnerException` and `__cause__` are propagated properly
- exceptions can now be converted with codecs
- `InnerException` and `__cause__` are propagated properly
- .NET arrays implement Python buffer protocol


### Changed
- Drop support for Python 2, 3.4, and 3.5
Expand Down
94 changes: 94 additions & 0 deletionssrc/embed_tests/NumPyTests.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Python.Runtime;
using Python.Runtime.Codecs;

namespace Python.EmbeddingTest
{
public class NumPyTests
{
[OneTimeSetUp]
public void SetUp()
{
PythonEngine.Initialize();
TupleCodec<ValueTuple>.Register();
}

[OneTimeTearDown]
public void Dispose()
{
PythonEngine.Shutdown();
}

[Test]
public void TestReadme()
{
dynamic np;
try
{
np = Py.Import("numpy");
}
catch (PythonException)
{
Assert.Inconclusive("Numpy or dependency not installed");
return;
}

Assert.AreEqual("1.0", np.cos(np.pi * 2).ToString());

dynamic sin = np.sin;
StringAssert.StartsWith("-0.95892", sin(5).ToString());

double c = np.cos(5) + sin(5);
Assert.AreEqual(-0.675262, c, 0.01);

dynamic a = np.array(new List<float> { 1, 2, 3 });
Assert.AreEqual("float64", a.dtype.ToString());

dynamic b = np.array(new List<float> { 6, 5, 4 }, Py.kw("dtype", np.int32));
Assert.AreEqual("int32", b.dtype.ToString());

Assert.AreEqual("[ 6. 10. 12.]", (a * b).ToString().Replace(" ", " "));
}

[Test]
public void MultidimensionalNumPyArray()
{
PyObject np;
try {
np = Py.Import("numpy");
} catch (PythonException) {
Assert.Inconclusive("Numpy or dependency not installed");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We should probably make thatnumpy is installed in our CI then, no?

return;
}

var array = new[,] { { 1, 2 }, { 3, 4 } };
var ndarray = np.InvokeMethod("asarray", array.ToPython());
Assert.AreEqual((2,2), ndarray.GetAttr("shape").As<(int,int)>());
Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As<int>());
Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As<int>());
}

[Test]
public void Int64Array()
{
PyObject np;
try
{
np = Py.Import("numpy");
}
catch (PythonException)
{
Assert.Inconclusive("Numpy or dependency not installed");
return;
}

var array = new long[,] { { 1, 2 }, { 3, 4 } };
var ndarray = np.InvokeMethod("asarray", array.ToPython());
Assert.AreEqual((2, 2), ndarray.GetAttr("shape").As<(int, int)>());
Assert.AreEqual(1, ndarray[(0, 0).ToPython()].InvokeMethod("__int__").As<long>());
Assert.AreEqual(array[1, 0], ndarray[(1, 0).ToPython()].InvokeMethod("__int__").As<long>());
}
}
}
53 changes: 0 additions & 53 deletionssrc/embed_tests/TestExample.cs
View file
Open in desktop

This file was deleted.

13 changes: 13 additions & 0 deletionssrc/embed_tests/TestPyBuffer.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
using System;
using System.Text;
using NUnit.Framework;
using Python.Runtime;
using Python.Runtime.Codecs;

namespace Python.EmbeddingTest {
class TestPyBuffer
Expand All@@ -9,6 +11,7 @@ class TestPyBuffer
public void SetUp()
{
PythonEngine.Initialize();
TupleCodec<ValueTuple>.Register();
}

[OneTimeTearDown]
Expand DownExpand Up@@ -64,5 +67,15 @@ public void TestBufferRead()
}
}
}

[Test]
public void ArrayHasBuffer()
{
var array = new[,] {{1, 2}, {3,4}};
var memoryView = PythonEngine.Eval("memoryview");
var mem = memoryView.Invoke(array.ToPython());
Assert.AreEqual(1, mem[(0, 0).ToPython()].As<int>());
Assert.AreEqual(array[1,0], mem[(1, 0).ToPython()].As<int>());
}
}
}
163 changes: 163 additions & 0 deletionssrc/runtime/arrayobject.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Python.Runtime
{
Expand DownExpand Up@@ -366,5 +368,166 @@ public static int sq_contains(IntPtr ob, IntPtr v)

return 0;
}

#region Buffer protocol
static int GetBuffer(BorrowedReference obj, out Py_buffer buffer, PyBUF flags)
{
buffer = default;

if (flags == PyBUF.SIMPLE)
{
Exceptions.SetError(Exceptions.BufferError, "SIMPLE not implemented");
return -1;
}
if ((flags & PyBUF.F_CONTIGUOUS) == PyBUF.F_CONTIGUOUS)
{
Exceptions.SetError(Exceptions.BufferError, "only C-contiguous supported");
return -1;
}
var self = (Array)((CLRObject)GetManagedObject(obj)).inst;
Type itemType = self.GetType().GetElementType();

bool formatRequested = (flags & PyBUF.FORMATS) != 0;
string format = GetFormat(itemType);
if (formatRequested && format is null)
{
Exceptions.SetError(Exceptions.BufferError, "unsupported element type: " + itemType.Name);
return -1;
}
GCHandle gcHandle;
try
{
gcHandle = GCHandle.Alloc(self, GCHandleType.Pinned);
} catch (ArgumentException ex)
{
Exceptions.SetError(Exceptions.BufferError, ex.Message);
return -1;
}

int itemSize = Marshal.SizeOf(itemType);
IntPtr[] shape = GetShape(self);
IntPtr[] strides = GetStrides(shape, itemSize);
buffer = new Py_buffer
{
buf = gcHandle.AddrOfPinnedObject(),
obj = Runtime.SelfIncRef(obj.DangerousGetAddress()),
len = (IntPtr)(self.LongLength*itemSize),
itemsize = (IntPtr)itemSize,
_readonly = false,
ndim = self.Rank,
format = format,
shape = ToUnmanaged(shape),
strides = (flags & PyBUF.STRIDES) == PyBUF.STRIDES ? ToUnmanaged(strides) : IntPtr.Zero,
suboffsets = IntPtr.Zero,
_internal = (IntPtr)gcHandle,
};

return 0;
}
static void ReleaseBuffer(BorrowedReference obj, ref Py_buffer buffer)
{
if (buffer._internal == IntPtr.Zero) return;

UnmanagedFree(ref buffer.shape);
UnmanagedFree(ref buffer.strides);
UnmanagedFree(ref buffer.suboffsets);

var gcHandle = (GCHandle)buffer._internal;
gcHandle.Free();
buffer._internal = IntPtr.Zero;
}

static IntPtr[] GetStrides(IntPtr[] shape, long itemSize)
{
var result = new IntPtr[shape.Length];
result[shape.Length - 1] = new IntPtr(itemSize);
for (int dim = shape.Length - 2; dim >= 0; dim--)
{
itemSize *= shape[dim + 1].ToInt64();
result[dim] = new IntPtr(itemSize);
}
return result;
}
static IntPtr[] GetShape(Array array)
{
var result = new IntPtr[array.Rank];
for (int i = 0; i < result.Length; i++)
result[i] = (IntPtr)array.GetLongLength(i);
return result;
}

static void UnmanagedFree(ref IntPtr address)
{
if (address == IntPtr.Zero) return;

Marshal.FreeHGlobal(address);
address = IntPtr.Zero;
}
static unsafe IntPtr ToUnmanaged<T>(T[] array) where T : unmanaged
{
IntPtr result = Marshal.AllocHGlobal(checked(Marshal.SizeOf(typeof(T)) * array.Length));
fixed (T* ptr = array)
{
var @out = (T*)result;
for (int i = 0; i < array.Length; i++)
@out[i] = ptr[i];
}
return result;
}

static readonly Dictionary<Type, string> ItemFormats = new Dictionary<Type, string>
{
[typeof(byte)] = "B",
[typeof(sbyte)] = "b",

[typeof(bool)] = "?",

[typeof(short)] = "h",
[typeof(ushort)] = "H",
// see https://github.com/pybind/pybind11/issues/1908#issuecomment-658358767
[typeof(int)] = "i",
[typeof(uint)] = "I",
[typeof(long)] = "q",
[typeof(ulong)] = "Q",

[typeof(IntPtr)] = "n",
[typeof(UIntPtr)] = "N",

// TODO: half = "e"
[typeof(float)] = "f",
[typeof(double)] = "d",
};

static string GetFormat(Type elementType)
=> ItemFormats.TryGetValue(elementType, out string result) ? result : null;

static readonly GetBufferProc getBufferProc = GetBuffer;
static readonly ReleaseBufferProc releaseBufferProc = ReleaseBuffer;
static readonly IntPtr BufferProcsAddress = AllocateBufferProcs();
static IntPtr AllocateBufferProcs()
{
var procs = new PyBufferProcs
{
Get = Marshal.GetFunctionPointerForDelegate(getBufferProc),
Release = Marshal.GetFunctionPointerForDelegate(releaseBufferProc),
};
IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(PyBufferProcs)));
Marshal.StructureToPtr(procs, result, fDeleteOld: false);
return result;
}
#endregion

/// <summary>
/// <see cref="TypeManager.InitializeSlots(IntPtr, Type, SlotsHolder)"/>
/// </summary>
public static void InitializeSlots(IntPtr type, ISet<string> initialized, SlotsHolder slotsHolder)
{
if (initialized.Add(nameof(TypeOffset.tp_as_buffer)))
{
// TODO: only for unmanaged arrays
int offset = TypeOffset.GetSlotOffset(nameof(TypeOffset.tp_as_buffer));
Marshal.WriteIntPtr(type, offset, BufferProcsAddress);
}
}
}
}
11 changes: 11 additions & 0 deletionssrc/runtime/bufferinterface.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,4 +103,15 @@ public enum PyBUF
/// </summary>
FULL_RO = (INDIRECT | FORMATS),
}

internal struct PyBufferProcs
{
public IntPtr Get;
public IntPtr Release;
}

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int GetBufferProc(BorrowedReference obj, out Py_buffer buffer, PyBUF flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void ReleaseBufferProc(BorrowedReference obj, ref Py_buffer buffer);
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp