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

implement list codec#1084

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
lostmsu merged 25 commits intopythonnet:masterfromkoubaa:list-codec
Feb 18, 2021
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
25 commits
Select commitHold shift + click to select a range
b500aa1
begin to implement list codec
koubaaMar 10, 2020
f80ae87
add a test
koubaaMar 10, 2020
765b6a2
improve CanDecode
koubaaMar 11, 2020
98ce49c
improve list codec
koubaaMar 11, 2020
b827f5a
full set of list codecs
koubaaMar 11, 2020
224df0e
abandon rank and use three codecs
koubaaMar 22, 2020
1600fdc
update
koubaaMar 25, 2020
c43446e
respond to PR comments
koubaaMar 29, 2020
32fa32d
make decoders public
koubaaMar 31, 2020
dfc814b
Make reset public
koubaaApr 1, 2020
f025cd1
add codec tests
koubaaApr 1, 2020
24d78c3
respond to comments
koubaaApr 6, 2020
ebcfa30
fix brace
koubaaAug 30, 2020
5cc575d
use unix line endings to avoid large diff
koubaaAug 30, 2020
17c47a7
fix compiler error
koubaaAug 30, 2020
e8cc3d8
don't rethrow exception
koubaaSep 13, 2020
080dbc3
cleanup
koubaaSep 17, 2020
059ab08
fixes
koubaaSep 18, 2020
432c09e
clean up sequence wrapper
koubaaSep 18, 2020
3043201
respond to comments
koubaaOct 10, 2020
57d7779
fix potential double free
koubaaOct 20, 2020
7fb5915
fix exception
koubaaFeb 17, 2021
085c665
fix all exceptions
koubaaFeb 17, 2021
bf2d038
respond to PR feedback
koubaaFeb 18, 2021
07ee9fb
use hasattr("__iter__")
koubaaFeb 18, 2021
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
250 changes: 232 additions & 18 deletionssrc/embed_tests/Codecs.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,39 @@
namespace Python.EmbeddingTest {
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using NUnit.Framework;
using Python.Runtime;
using Python.Runtime.Codecs;

public class Codecs {
public class Codecs
{
[SetUp]
public void SetUp() {
public void SetUp()
{
PythonEngine.Initialize();
}

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

[Test]
public void ConversionsGeneric() {
ConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
public void TupleConversionsGeneric()
{
TupleConversionsGeneric<ValueTuple<int, string, object>, ValueTuple>();
}

static void ConversionsGeneric<T, TTuple>() {
static void TupleConversionsGeneric<T, TTuple>()
{
TupleCodec<TTuple>.Register();
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
T restored = default;
using (Py.GIL())
using (var scope = Py.CreateScope()) {
using (var scope = Py.CreateScope())
{
void Accept(T value) => restored = value;
var accept = new Action<T>(Accept).ToPython();
scope.Set(nameof(tuple), tuple);
Expand All@@ -38,15 +44,18 @@ static void ConversionsGeneric<T, TTuple>() {
}

[Test]
public void ConversionsObject() {
ConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
public void TupleConversionsObject()
{
TupleConversionsObject<ValueTuple<int, string, object>, ValueTuple>();
}
static void ConversionsObject<T, TTuple>() {
static void TupleConversionsObject<T, TTuple>()
{
TupleCodec<TTuple>.Register();
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
T restored = default;
using (Py.GIL())
using (var scope = Py.CreateScope()) {
using (var scope = Py.CreateScope())
{
void Accept(object value) => restored = (T)value;
var accept = new Action<object>(Accept).ToPython();
scope.Set(nameof(tuple), tuple);
Expand All@@ -57,31 +66,236 @@ static void ConversionsObject<T, TTuple>() {
}

[Test]
public void TupleRoundtripObject() {
public void TupleRoundtripObject()
{
TupleRoundtripObject<ValueTuple<int, string, object>, ValueTuple>();
}
static void TupleRoundtripObject<T, TTuple>() {
static void TupleRoundtripObject<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
using (Py.GIL()) {
using (Py.GIL())
{
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out object restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
}

[Test]
public void TupleRoundtripGeneric() {
public void TupleRoundtripGeneric()
{
TupleRoundtripGeneric<ValueTuple<int, string, object>, ValueTuple>();
}

static void TupleRoundtripGeneric<T, TTuple>() {
static void TupleRoundtripGeneric<T, TTuple>()
{
var tuple = Activator.CreateInstance(typeof(T), 42, "42", new object());
using (Py.GIL()) {
using (Py.GIL())
{
var pyTuple = TupleCodec<TTuple>.Instance.TryEncode(tuple);
Assert.IsTrue(TupleCodec<TTuple>.Instance.TryDecode(pyTuple, out T restored));
Assert.AreEqual(expected: tuple, actual: restored);
}
}

static PyObject GetPythonIterable()
{
using (Py.GIL())
{
return PythonEngine.Eval("map(lambda x: x, [1,2,3])");
}
}

[Test]
public void ListDecoderTest()
{
var codec = ListDecoder.Instance;
var items = new List<PyObject>() { new PyInt(1), new PyInt(2), new PyInt(3) };

var pyList = new PyList(items.ToArray());

var pyListType = pyList.GetPythonType();
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList<bool>)));
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IList<int>)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection<float>)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool)));

//we'd have to copy into a list instance to do this, it would not be lossless.
//lossy converters can be implemented outside of the python.net core library
Assert.IsFalse(codec.CanDecode(pyListType, typeof(List<int>)));

//convert to list of int
IList<int> intList = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intList); });
CollectionAssert.AreEqual(intList, new List<object> { 1, 2, 3 });

//convert to list of string. This will not work.
//The ListWrapper class will throw a python exception when it tries to access any element.
//TryDecode is a lossless conversion so there will be no exception at that point
//interestingly, since the size of the python list can be queried without any conversion,
//the IList will report a Count of 3.
IList<string> stringList = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringList); });
Assert.AreEqual(stringList.Count, 3);
Assert.Throws(typeof(InvalidCastException), () => { var x = stringList[0]; });

//can't convert python iterable to list (this will require a copy which isn't lossless)
var foo = GetPythonIterable();
var fooType = foo.GetPythonType();
Assert.IsFalse(codec.CanDecode(fooType, typeof(IList<int>)));
}

[Test]
public void SequenceDecoderTest()
{
var codec = SequenceDecoder.Instance;
var items = new List<PyObject>() { new PyInt(1), new PyInt(2), new PyInt(3) };

//SequenceConverter can only convert to any ICollection
var pyList = new PyList(items.ToArray());
//it can convert a PyList, since PyList satisfies the python sequence protocol

Assert.IsFalse(codec.CanDecode(pyList, typeof(bool)));
Assert.IsFalse(codec.CanDecode(pyList, typeof(IList<int>)));
Assert.IsFalse(codec.CanDecode(pyList, typeof(System.Collections.IEnumerable)));
Assert.IsFalse(codec.CanDecode(pyList, typeof(IEnumerable<int>)));

Assert.IsTrue(codec.CanDecode(pyList, typeof(ICollection<float>)));
Assert.IsTrue(codec.CanDecode(pyList, typeof(ICollection<string>)));
Assert.IsTrue(codec.CanDecode(pyList, typeof(ICollection<int>)));

//convert to collection of int
ICollection<int> intCollection = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intCollection); });
CollectionAssert.AreEqual(intCollection, new List<object> { 1, 2, 3 });

//no python exception should have occurred during the above conversion and check
Runtime.CheckExceptionOccurred();

//convert to collection of string. This will not work.
//The SequenceWrapper class will throw a python exception when it tries to access any element.
//TryDecode is a lossless conversion so there will be no exception at that point
//interestingly, since the size of the python sequence can be queried without any conversion,
//the IList will report a Count of 3.
ICollection<string> stringCollection = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringCollection); });
Assert.AreEqual(3, stringCollection.Count());
Assert.Throws(typeof(InvalidCastException), () => {
string[] array = new string[3];
stringCollection.CopyTo(array, 0);
});

Runtime.CheckExceptionOccurred();

//can't convert python iterable to collection (this will require a copy which isn't lossless)
//python iterables do not satisfy the python sequence protocol
var foo = GetPythonIterable();
var fooType = foo.GetPythonType();
Assert.IsFalse(codec.CanDecode(fooType, typeof(ICollection<int>)));

//python tuples do satisfy the python sequence protocol
var pyTuple = new PyTuple(items.ToArray());
var pyTupleType = pyTuple.GetPythonType();

Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<float>)));
Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<int>)));
Assert.IsTrue(codec.CanDecode(pyTupleType, typeof(ICollection<string>)));

//convert to collection of int
ICollection<int> intCollection2 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out intCollection2); });
CollectionAssert.AreEqual(intCollection2, new List<object> { 1, 2, 3 });

//no python exception should have occurred during the above conversion and check
Runtime.CheckExceptionOccurred();

//convert to collection of string. This will not work.
//The SequenceWrapper class will throw a python exception when it tries to access any element.
//TryDecode is a lossless conversion so there will be no exception at that point
//interestingly, since the size of the python sequence can be queried without any conversion,
//the IList will report a Count of 3.
ICollection<string> stringCollection2 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyTuple, out stringCollection2); });
Assert.AreEqual(3, stringCollection2.Count());
Assert.Throws(typeof(InvalidCastException), () => {
string[] array = new string[3];
stringCollection2.CopyTo(array, 0);
});

Runtime.CheckExceptionOccurred();

}

[Test]
public void IterableDecoderTest()
{
var codec = IterableDecoder.Instance;
var items = new List<PyObject>() { new PyInt(1), new PyInt(2), new PyInt(3) };

var pyList = new PyList(items.ToArray());
var pyListType = pyList.GetPythonType();
Assert.IsFalse(codec.CanDecode(pyListType, typeof(IList<bool>)));
Assert.IsTrue(codec.CanDecode(pyListType, typeof(System.Collections.IEnumerable)));
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(ICollection<float>)));
Assert.IsFalse(codec.CanDecode(pyListType, typeof(bool)));

//ensure a PyList can be converted to a plain IEnumerable
System.Collections.IEnumerable plainEnumerable1 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable1); });
CollectionAssert.AreEqual(plainEnumerable1, new List<object> { 1, 2, 3 });

//can convert to any generic ienumerable. If the type is not assignable from the python element
//it will lead to an empty iterable when decoding. TODO - should it throw?
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<int>)));
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<double>)));
Assert.IsTrue(codec.CanDecode(pyListType, typeof(IEnumerable<string>)));

IEnumerable<int> intEnumerable = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); });
CollectionAssert.AreEqual(intEnumerable, new List<object> { 1, 2, 3 });

Runtime.CheckExceptionOccurred();

IEnumerable<double> doubleEnumerable = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out doubleEnumerable); });
CollectionAssert.AreEqual(doubleEnumerable, new List<object> { 1, 2, 3 });

Runtime.CheckExceptionOccurred();

IEnumerable<string> stringEnumerable = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out stringEnumerable); });

Assert.Throws(typeof(InvalidCastException), () => {
foreach (string item in stringEnumerable)
{
var x = item;
}
});
Assert.Throws(typeof(InvalidCastException), () => {
stringEnumerable.Count();
});

Runtime.CheckExceptionOccurred();

//ensure a python class which implements the iterator protocol can be converter to a plain IEnumerable
var foo = GetPythonIterable();
var fooType = foo.GetPythonType();
System.Collections.IEnumerable plainEnumerable2 = null;
Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out plainEnumerable2); });
CollectionAssert.AreEqual(plainEnumerable2, new List<object> { 1, 2, 3 });

//can convert to any generic ienumerable. If the type is not assignable from the python element
//it will be an exception during TryDecode
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<int>)));
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<double>)));
Assert.IsTrue(codec.CanDecode(fooType, typeof(IEnumerable<string>)));

Assert.DoesNotThrow(() => { codec.TryDecode(pyList, out intEnumerable); });
CollectionAssert.AreEqual(intEnumerable, new List<object> { 1, 2, 3 });
}
}

/// <summary>
Expand Down
55 changes: 55 additions & 0 deletionssrc/runtime/Codecs/IterableDecoder.cs
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;

namespace Python.Runtime.Codecs
{
public class IterableDecoder : IPyObjectDecoder
{
internal static bool IsIterable(Type targetType)
{
//if it is a plain IEnumerable, we can decode it using sequence protocol.
if (targetType == typeof(System.Collections.IEnumerable))
return true;

if (!targetType.IsGenericType)
return false;

return targetType.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}

internal static bool IsIterable(PyObject objectType)
{
return objectType.HasAttr("__iter__");
}

public bool CanDecode(PyObject objectType, Type targetType)
{
return IsIterable(objectType) && IsIterable(targetType);
}

public bool TryDecode<T>(PyObject pyObj, out T value)
{
//first see if T is a plan IEnumerable
if (typeof(T) == typeof(System.Collections.IEnumerable))
{
object enumerable = new CollectionWrappers.IterableWrapper<object>(pyObj);
value = (T)enumerable;
return true;
}

var elementType = typeof(T).GetGenericArguments()[0];
var collectionType = typeof(CollectionWrappers.IterableWrapper<>).MakeGenericType(elementType);

var instance = Activator.CreateInstance(collectionType, new[] { pyObj });
value = (T)instance;
return true;
}

public static IterableDecoder Instance { get; } = new IterableDecoder();

public static void Register()
{
PyObjectConversions.RegisterDecoder(Instance);
}
}
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp