From 80377768f90973677e5ce5d282ee1fd6748b59c9 Mon Sep 17 00:00:00 2001 From: John Wilkes Date: Fri, 15 Jun 2018 18:07:34 -0500 Subject: [PATCH 1/4] Implementing GetDynamicMemberNames() for PyObject --- CHANGELOG.md | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/TestPyObject.cs | 90 +++++++++++++++++++++ src/runtime/pyobject.cs | 16 ++++ 4 files changed, 108 insertions(+) create mode 100644 src/embed_tests/TestPyObject.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d39fc444..c30b4c393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added `clr.GetClrType` (#432, #433) - Allowed passing `None` for nullable args (#460) - Added keyword arguments based on C# syntax for calling CPython methods (#461) +- Implemented GetDynamicMemberNames() for PyObject to allow dynamic object members to be visible in the debugger (#443) ### Changed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index fe02b0526..66e8c7165 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -93,6 +93,7 @@ + diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs new file mode 100644 index 000000000..907bc90b3 --- /dev/null +++ b/src/embed_tests/TestPyObject.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestPyObject + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestGetDynamicMemberNames() + { + List expectedMemberNames = new List + { + "__class__", + "__delattr__", + "__dict__", + "__dir__", + "__doc__", + "__eq__", + "__format__", + "__ge__", + "__getattribute__", + "__gt__", + "__hash__", + "__init__", + "__init_subclass__", + "__le__", + "__lt__", + "__module__", + "__ne__", + "__new__", + "__reduce__", + "__reduce_ex__", + "__repr__", + "__setattr__", + "__sizeof__", + "__str__", + "__subclasshook__", + "__weakref__", + "add", + "getNumber", + "member1", + "member2" + }; + + PyDict locals = new PyDict(); + + PythonEngine.Exec(@" +class MemberNamesTest: + def __init__(self): + self.member1 = 123 + self.member2 = 'Test string' + + def getNumber(self): + return 123 + + def add(self, x, y): + return x + y + +a = MemberNamesTest() +", null, locals.Handle); + + PyObject a = locals.GetItem("a"); + + IEnumerable membernames = a.GetDynamicMemberNames(); + + Assert.AreEqual(expectedMemberNames.Count, membernames.Count(), "Unexpected number of members."); + + IEnumerable> results = expectedMemberNames.Zip(membernames, (x, y) => new Tuple(x, y)); + foreach (Tuple result in results) + { + Assert.AreEqual(result.Item1, result.Item2, "Unexpected member name."); + } + } + } +} diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 4da74f96a..0e075824a 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Generic; using System.Dynamic; using System.Linq.Expressions; @@ -1238,5 +1239,20 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object r result = CheckNone(new PyObject(res)); return true; } + + /// + /// Returns the enumeration of all dynamic member names. + /// + /// + /// This method exists for debugging purposes only. + /// + /// A sequence that contains dynamic member names. + public override IEnumerable GetDynamicMemberNames() + { + foreach (PyObject pyObj in Dir()) + { + yield return pyObj.ToString(); + } + } } } From 23aef26fd5754c68e10a1377004a21f436305730 Mon Sep 17 00:00:00 2001 From: John Wilkes <> Date: Sun, 17 Jun 2018 14:54:51 -0500 Subject: [PATCH 2/4] Fixing unit test --- src/embed_tests/TestPyObject.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index 907bc90b3..bc8d9090d 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -28,20 +28,34 @@ public void TestGetDynamicMemberNames() "__class__", "__delattr__", "__dict__", +#if PYTHON3 "__dir__", +#endif "__doc__", +#if PYTHON3 "__eq__", +#endif "__format__", +#if PYTHON3 "__ge__", +#endif "__getattribute__", +#if PYTHON3 "__gt__", +#endif "__hash__", "__init__", +#if PYTHON36 "__init_subclass__", +#endif +#if PYTHON3 "__le__", "__lt__", +#endif "__module__", +#if PYTHON3 "__ne__", +#endif "__new__", "__reduce__", "__reduce_ex__", @@ -60,7 +74,7 @@ public void TestGetDynamicMemberNames() PyDict locals = new PyDict(); PythonEngine.Exec(@" -class MemberNamesTest: +class MemberNamesTest(object): def __init__(self): self.member1 = 123 self.member2 = 'Test string' From 56e52684d99d803576ea6d58d2245019555ea671 Mon Sep 17 00:00:00 2001 From: John Wilkes <> Date: Sun, 17 Jun 2018 15:31:09 -0500 Subject: [PATCH 3/4] Adding python version defines to unit test project --- src/embed_tests/Python.EmbeddingTest.csproj | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 66e8c7165..72014c908 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -26,45 +26,41 @@ true - DEBUG;TRACE + PYTHON2;PYTHON27;UCS4;TRACE;DEBUG full - - + PYTHON2;PYTHON27;UCS4 true pdbonly true - DEBUG;TRACE + PYTHON2;PYTHON27;UCS2;TRACE;DEBUG full - - + PYTHON2;PYTHON27;UCS2 true pdbonly true - DEBUG;TRACE + PYTHON3;PYTHON36;UCS4;TRACE;DEBUG full - - + PYTHON3;PYTHON36;UCS4 true pdbonly true - DEBUG;TRACE + PYTHON3;PYTHON36;UCS2;TRACE;DEBUG full - - + PYTHON3;PYTHON36;UCS2 true pdbonly From 28a0d1ef443585bb0ed0c9ad0b6e3983d3391ce8 Mon Sep 17 00:00:00 2001 From: John Wilkes <> Date: Mon, 18 Jun 2018 07:25:55 -0500 Subject: [PATCH 4/4] Addressing code review comments This reverts commit 56e52684d99d803576ea6d58d2245019555ea671. --- AUTHORS.md | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 20 +++++---- src/embed_tests/TestPyObject.cs | 49 ++------------------- 3 files changed, 16 insertions(+), 54 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 98cb1af39..5917944a8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -28,6 +28,7 @@ -   Jeff Reback ([@jreback](https://github.com/jreback)) - Joe Frayne ([@jfrayne](https://github.com/jfrayne)) - John Burnett ([@johnburnett](https://github.com/johnburnett)) +- John Wilkes ([@jbw3](https://github.com/jbw3)) - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 72014c908..66e8c7165 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -26,41 +26,45 @@ true - PYTHON2;PYTHON27;UCS4;TRACE;DEBUG + DEBUG;TRACE full - PYTHON2;PYTHON27;UCS4 + + true pdbonly true - PYTHON2;PYTHON27;UCS2;TRACE;DEBUG + DEBUG;TRACE full - PYTHON2;PYTHON27;UCS2 + + true pdbonly true - PYTHON3;PYTHON36;UCS4;TRACE;DEBUG + DEBUG;TRACE full - PYTHON3;PYTHON36;UCS4 + + true pdbonly true - PYTHON3;PYTHON36;UCS2;TRACE;DEBUG + DEBUG;TRACE full - PYTHON3;PYTHON36;UCS2 + + true pdbonly diff --git a/src/embed_tests/TestPyObject.cs b/src/embed_tests/TestPyObject.cs index bc8d9090d..d794ce06e 100644 --- a/src/embed_tests/TestPyObject.cs +++ b/src/embed_tests/TestPyObject.cs @@ -25,46 +25,6 @@ public void TestGetDynamicMemberNames() { List expectedMemberNames = new List { - "__class__", - "__delattr__", - "__dict__", -#if PYTHON3 - "__dir__", -#endif - "__doc__", -#if PYTHON3 - "__eq__", -#endif - "__format__", -#if PYTHON3 - "__ge__", -#endif - "__getattribute__", -#if PYTHON3 - "__gt__", -#endif - "__hash__", - "__init__", -#if PYTHON36 - "__init_subclass__", -#endif -#if PYTHON3 - "__le__", - "__lt__", -#endif - "__module__", -#if PYTHON3 - "__ne__", -#endif - "__new__", - "__reduce__", - "__reduce_ex__", - "__repr__", - "__setattr__", - "__sizeof__", - "__str__", - "__subclasshook__", - "__weakref__", "add", "getNumber", "member1", @@ -90,14 +50,11 @@ def add(self, x, y): PyObject a = locals.GetItem("a"); - IEnumerable membernames = a.GetDynamicMemberNames(); + IEnumerable memberNames = a.GetDynamicMemberNames(); - Assert.AreEqual(expectedMemberNames.Count, membernames.Count(), "Unexpected number of members."); - - IEnumerable> results = expectedMemberNames.Zip(membernames, (x, y) => new Tuple(x, y)); - foreach (Tuple result in results) + foreach (string expectedName in expectedMemberNames) { - Assert.AreEqual(result.Item1, result.Item2, "Unexpected member name."); + Assert.IsTrue(memberNames.Contains(expectedName), "Could not find member '{0}'.", expectedName); } } }