From 351d9ff22436d84fa27869a97ea54e9822d54721 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sat, 19 Jan 2019 20:33:09 -0600 Subject: [PATCH 1/8] Provide hook to implement __repr__ Issue: 680 --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/runtime/classbase.cs | 68 +++++++++++++++++++++ src/runtime/exceptions.cs | 23 +++++++ src/testing/Python.Test.csproj | 1 + src/testing/ReprTest.cs | 108 +++++++++++++++++++++++++++++++++ src/tests/test_exceptions.py | 7 +-- src/tests/test_repr.py | 74 ++++++++++++++++++++++ src/tests/tests.pyproj | 1 + 9 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/testing/ReprTest.cs create mode 100644 src/tests/test_repr.py diff --git a/AUTHORS.md b/AUTHORS.md index fe2d2b172..f8eff412e 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -36,6 +36,7 @@ - Luke Stratman ([@lstratman](https://github.com/lstratman)) - Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy)) - Matthias Dittrich ([@matthid](https://github.com/matthid)) +- Mohamed Koubaa ([@koubaa](https://github.com/koubaa)) - Patrick Stewart ([@patstew](https://github.com/patstew)) - Raphael Nestler ([@rnestler](https://github.com/rnestler)) - Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch)) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3816cd0c..beccf4b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Incorporated reference-style links to issues and pull requests in the CHANGELOG ([#608][i608]) - Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625]) - Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698]) +- Added support for C# types to provide `__repr__` ([#680][p680]) ### Changed diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 5846fa82a..825d969b2 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -233,7 +233,75 @@ public static IntPtr tp_str(IntPtr ob) } try { + //As per python doc: + //The return value must be a string object. If a class defines __repr__() but not __str__(), + //then __repr__() is also used when an “informal” string representation of instances of that + //class is required. + //In C#, everything provides ToString(), so the check here will be whether the type explicitly + //provides ToString() or if it is language provided (i.e. the fully qualified type name as a string) + + //First check which type in the object hierarchy provides ToString() + //ToString has two "official" overloads so loop over GetMethods to get the one without parameters + var instType = co.inst.GetType(); + foreach (var method in instType.GetMethods()) + { + + //TODO this could probably be done more cleanly with Linq + if (!method.IsPublic) continue; //skip private/protected methods + if (method.Name != "ToString") continue; //only look for ToString + if (method.DeclaringType == typeof(object)) continue; //ignore default from object + if (method.GetParameters().Length != 0) continue; //ignore Formatter overload of ToString + + //match! something other than object provides a parameter-less overload of ToString + return Runtime.PyString_FromString(co.inst.ToString()); + } + + //If the object defines __repr__, call it. + System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); + if (reprMethodInfo != null && reprMethodInfo.IsPublic) + { + var reprString = reprMethodInfo.Invoke(co.inst, null) as string; + return Runtime.PyString_FromString(reprString); + } + + //otherwise fallback to object's ToString() implementation return Runtime.PyString_FromString(co.inst.ToString()); + + } + catch (Exception e) + { + if (e.InnerException != null) + { + e = e.InnerException; + } + Exceptions.SetError(e); + return IntPtr.Zero; + } + } + + public static IntPtr tp_repr(IntPtr ob) + { + var co = GetManagedObject(ob) as CLRObject; + if (co == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + try + { + //if __repr__ is defined, use it + var instType = co.inst.GetType(); + System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__"); + if (methodInfo != null && methodInfo.IsPublic) + { + var reprString = methodInfo.Invoke(co.inst, null) as string; + return Runtime.PyString_FromString(reprString); + } + + //otherwise use the standard object.__repr__(inst) + IntPtr args = Runtime.PyTuple_New(1); + Runtime.PyTuple_SetItem(args, 0, ob); + IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); + return Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); } catch (Exception e) { diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs index 8bed0abfd..31c367eb2 100644 --- a/src/runtime/exceptions.cs +++ b/src/runtime/exceptions.cs @@ -36,6 +36,29 @@ internal static Exception ToException(IntPtr ob) return e; } + /// + /// Exception __repr__ implementation + /// + public new static IntPtr tp_repr(IntPtr ob) + { + Exception e = ToException(ob); + if (e == null) + { + return Exceptions.RaiseTypeError("invalid object"); + } + string name = e.GetType().Name; + string message; + if (e.Message != String.Empty) + { + message = String.Format("{0}('{1}')", name, e.Message); + } + else + { + message = String.Format("{0}()", name); + } + return Runtime.PyUnicode_FromString(message); + } + /// /// Exception __str__ implementation /// diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index 27639ed5a..b15bd91de 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -91,6 +91,7 @@ + diff --git a/src/testing/ReprTest.cs b/src/testing/ReprTest.cs new file mode 100644 index 000000000..48e93683a --- /dev/null +++ b/src/testing/ReprTest.cs @@ -0,0 +1,108 @@ +using System; +using System.Text; + +namespace Python.Test +{ + /// + /// Supports repr unit tests. + /// + public class ReprTest + { + public class Point + { + public Point(double x, double y) + { + X = x; + Y = y; + } + + public double X { get; set; } + public double Y { get; set; } + + public override string ToString() + { + return base.ToString() + ": X=" + X.ToString() + ", Y=" + Y.ToString(); + } + + public string __repr__() + { + return "Point(" + X.ToString() + "," + Y.ToString() + ")"; + } + } + + public class Foo + { + public string __repr__() + { + return "I implement __repr__() but not ToString()!"; + } + } + + public class Bar + { + public override string ToString() + { + return "I implement ToString() but not __repr__()!"; + } + } + + public class BazBase + { + public override string ToString() + { + return "Base class implementing ToString()!"; + } + } + + public class BazMiddle : BazBase + { + public override string ToString() + { + return "Middle class implementing ToString()!"; + } + } + + //implements ToString via BazMiddle + public class Baz : BazMiddle + { + + } + + public class Quux + { + public string ToString(string format) + { + return "I implement ToString() with an argument!"; + } + } + + public class QuuzBase + { + protected string __repr__() + { + return "I implement __repr__ but it isn't public!"; + } + } + + public class Quuz : QuuzBase + { + + } + + public class Corge + { + public string __repr__(int i) + { + return "__repr__ implemention with input parameter!"; + } + } + + public class Grault + { + public int __repr__() + { + return "__repr__ implemention with wrong return type!".Length; + } + } + } +} diff --git a/src/tests/test_exceptions.py b/src/tests/test_exceptions.py index 08b00d77d..90558dd0f 100644 --- a/src/tests/test_exceptions.py +++ b/src/tests/test_exceptions.py @@ -288,11 +288,8 @@ def test_python_compat_of_managed_exceptions(): assert e.args == (msg,) assert isinstance(e.args, tuple) - if PY3: - strexp = "OverflowException('Simple message" - assert repr(e)[:len(strexp)] == strexp - elif PY2: - assert repr(e) == "OverflowException(u'Simple message',)" + strexp = "OverflowException('Simple message" + assert repr(e)[:len(strexp)] == strexp def test_exception_is_instance_of_system_object(): diff --git a/src/tests/test_repr.py b/src/tests/test_repr.py new file mode 100644 index 000000000..c7167eaea --- /dev/null +++ b/src/tests/test_repr.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +"""Test __repr__ output""" + +import System +import pytest +from Python.Test import ReprTest + +def test_basic(): + """Test Point class which implements both ToString and __repr__ without inheritance""" + ob = ReprTest.Point(1,2) + # point implements ToString() and __repr__() + assert ob.__repr__() == "Point(1,2)" + assert str(ob) == "Python.Test.ReprTest+Point: X=1, Y=2" + +def test_system_string(): + """Test system string""" + ob = System.String("hello") + assert str(ob) == "hello" + assert " + From 2a002d74eb74c01cc257a5ba87590d43a8df0727 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Fri, 8 Mar 2019 17:50:51 -0600 Subject: [PATCH 2/8] fix casing --- src/testing/Python.Test.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/testing/Python.Test.csproj b/src/testing/Python.Test.csproj index b15bd91de..6bf5c2d22 100644 --- a/src/testing/Python.Test.csproj +++ b/src/testing/Python.Test.csproj @@ -91,7 +91,7 @@ - + @@ -112,4 +112,4 @@ - \ No newline at end of file + From 0916406aafa36c171553d50b4a4627d0b006a1a2 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 15 Sep 2019 13:32:15 -0500 Subject: [PATCH 3/8] update based on review --- src/runtime/classbase.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 825d969b2..f604e4aa3 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -242,25 +242,18 @@ public static IntPtr tp_str(IntPtr ob) //First check which type in the object hierarchy provides ToString() //ToString has two "official" overloads so loop over GetMethods to get the one without parameters - var instType = co.inst.GetType(); - foreach (var method in instType.GetMethods()) - { - - //TODO this could probably be done more cleanly with Linq - if (!method.IsPublic) continue; //skip private/protected methods - if (method.Name != "ToString") continue; //only look for ToString - if (method.DeclaringType == typeof(object)) continue; //ignore default from object - if (method.GetParameters().Length != 0) continue; //ignore Formatter overload of ToString - + var method = type.GetMethod("ToString", new Type[]{}); + if (method.DeclaringTyppe != typeof(object)) + { //match! something other than object provides a parameter-less overload of ToString - return Runtime.PyString_FromString(co.inst.ToString()); - } + return Runtime.Pystring_FromString(co.inst.ToString()); + } //If the object defines __repr__, call it. System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); if (reprMethodInfo != null && reprMethodInfo.IsPublic) { - var reprString = reprMethodInfo.Invoke(co.inst, null) as string; + var reprString = (string)reprMethodInfo.Invoke(co.inst, null); return Runtime.PyString_FromString(reprString); } From 2fb6a430a0eb4f271cfdc9229bbe55ac8bc5b85e Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 15 Sep 2019 13:41:28 -0500 Subject: [PATCH 4/8] fix whitespace --- src/runtime/classbase.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index f604e4aa3..b633f1154 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -242,12 +242,12 @@ public static IntPtr tp_str(IntPtr ob) //First check which type in the object hierarchy provides ToString() //ToString has two "official" overloads so loop over GetMethods to get the one without parameters - var method = type.GetMethod("ToString", new Type[]{}); - if (method.DeclaringTyppe != typeof(object)) - { + var method = type.GetMethod("ToString", new Type[]{}); + if (method.DeclaringTyppe != typeof(object)) + { //match! something other than object provides a parameter-less overload of ToString - return Runtime.Pystring_FromString(co.inst.ToString()); - } + return Runtime.Pystring_FromString(co.inst.ToString()); + } //If the object defines __repr__, call it. System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); From 9fc5b5fc585f340b3c69172b558414616d12f65f Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 15 Sep 2019 13:54:31 -0500 Subject: [PATCH 5/8] fix whitespace --- src/runtime/classbase.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index b633f1154..dfdb2feb3 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -242,12 +242,12 @@ public static IntPtr tp_str(IntPtr ob) //First check which type in the object hierarchy provides ToString() //ToString has two "official" overloads so loop over GetMethods to get the one without parameters - var method = type.GetMethod("ToString", new Type[]{}); - if (method.DeclaringTyppe != typeof(object)) - { + var method = type.GetMethod("ToString", new Type[]{}); + if (method.DeclaringTyppe != typeof(object)) + { //match! something other than object provides a parameter-less overload of ToString - return Runtime.Pystring_FromString(co.inst.ToString()); - } + return Runtime.Pystring_FromString(co.inst.ToString()); + } //If the object defines __repr__, call it. System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); From 00b55db17ae0e3d15d0ff2bda6ead06fac06b040 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Sun, 15 Sep 2019 14:40:06 -0500 Subject: [PATCH 6/8] fix --- src/runtime/classbase.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index dfdb2feb3..56bbaf35b 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -242,11 +242,12 @@ public static IntPtr tp_str(IntPtr ob) //First check which type in the object hierarchy provides ToString() //ToString has two "official" overloads so loop over GetMethods to get the one without parameters - var method = type.GetMethod("ToString", new Type[]{}); - if (method.DeclaringTyppe != typeof(object)) + var instType = co.inst.GetType(); + var method = instType.GetMethod("ToString", new Type[]{}); + if (method.DeclaringType != typeof(object)) { //match! something other than object provides a parameter-less overload of ToString - return Runtime.Pystring_FromString(co.inst.ToString()); + return Runtime.PyString_FromString(co.inst.ToString()); } //If the object defines __repr__, call it. From accc7978c657f0e524eb32f1d2a4cdc2a6e08db1 Mon Sep 17 00:00:00 2001 From: Mohamed Koubaa Date: Wed, 16 Oct 2019 17:56:01 -0500 Subject: [PATCH 7/8] Don't use repr in __str__. Clean up unneeded tests --- src/runtime/classbase.cs | 27 --------------------------- src/tests/test_repr.py | 6 ------ 2 files changed, 33 deletions(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 56bbaf35b..86b1de27b 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -233,34 +233,7 @@ public static IntPtr tp_str(IntPtr ob) } try { - //As per python doc: - //The return value must be a string object. If a class defines __repr__() but not __str__(), - //then __repr__() is also used when an “informal” string representation of instances of that - //class is required. - //In C#, everything provides ToString(), so the check here will be whether the type explicitly - //provides ToString() or if it is language provided (i.e. the fully qualified type name as a string) - - //First check which type in the object hierarchy provides ToString() - //ToString has two "official" overloads so loop over GetMethods to get the one without parameters - var instType = co.inst.GetType(); - var method = instType.GetMethod("ToString", new Type[]{}); - if (method.DeclaringType != typeof(object)) - { - //match! something other than object provides a parameter-less overload of ToString - return Runtime.PyString_FromString(co.inst.ToString()); - } - - //If the object defines __repr__, call it. - System.Reflection.MethodInfo reprMethodInfo = instType.GetMethod("__repr__"); - if (reprMethodInfo != null && reprMethodInfo.IsPublic) - { - var reprString = (string)reprMethodInfo.Invoke(co.inst, null); - return Runtime.PyString_FromString(reprString); - } - - //otherwise fallback to object's ToString() implementation return Runtime.PyString_FromString(co.inst.ToString()); - } catch (Exception e) { diff --git a/src/tests/test_repr.py b/src/tests/test_repr.py index c7167eaea..d120b0c4c 100644 --- a/src/tests/test_repr.py +++ b/src/tests/test_repr.py @@ -19,12 +19,6 @@ def test_system_string(): assert str(ob) == "hello" assert " Date: Wed, 16 Oct 2019 17:59:55 -0500 Subject: [PATCH 8/8] fix leak --- src/runtime/classbase.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs index 86b1de27b..41636c404 100644 --- a/src/runtime/classbase.cs +++ b/src/runtime/classbase.cs @@ -268,7 +268,10 @@ public static IntPtr tp_repr(IntPtr ob) IntPtr args = Runtime.PyTuple_New(1); Runtime.PyTuple_SetItem(args, 0, ob); IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__"); - return Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); + var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero); + Runtime.XDecref(args); + Runtime.XDecref(reprFunc); + return output; } catch (Exception e) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy