Skip to content

Commit 4a9457f

Browse files
koubaafilmor
authored andcommitted
Provide hook to implement __repr__ (#808)
Provide hook to implement __repr__
1 parent 60e6045 commit 4a9457f

File tree

9 files changed

+244
-6
lines changed

9 files changed

+244
-6
lines changed

AUTHORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- Luke Stratman ([@lstratman](https://github.com/lstratman))
4242
- Konstantin Posudevskiy ([@konstantin-posudevskiy](https://github.com/konstantin-posudevskiy))
4343
- Matthias Dittrich ([@matthid](https://github.com/matthid))
44+
- Mohamed Koubaa ([@koubaa](https://github.com/koubaa))
4445
- Patrick Stewart ([@patstew](https://github.com/patstew))
4546
- Raphael Nestler ([@rnestler](https://github.com/rnestler))
4647
- Rickard Holmberg ([@rickardraysearch](https://github.com/rickardraysearch))

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
4646
- Added PyObject finalizer support, Python objects referred by C# can be auto collect now ([#692][p692]).
4747
- Added detailed comments about aproaches and dangers to handle multi-app-domains ([#625][p625])
4848
- Python 3.7 support, builds and testing added. Defaults changed from Python 3.6 to 3.7 ([#698][p698])
49+
- Added support for C# types to provide `__repr__` ([#680][p680])
4950

5051
### Changed
5152

src/runtime/classbase.cs

+38
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,44 @@ public static IntPtr tp_str(IntPtr ob)
246246
}
247247
}
248248

249+
public static IntPtr tp_repr(IntPtr ob)
250+
{
251+
var co = GetManagedObject(ob) as CLRObject;
252+
if (co == null)
253+
{
254+
return Exceptions.RaiseTypeError("invalid object");
255+
}
256+
try
257+
{
258+
//if __repr__ is defined, use it
259+
var instType = co.inst.GetType();
260+
System.Reflection.MethodInfo methodInfo = instType.GetMethod("__repr__");
261+
if (methodInfo != null && methodInfo.IsPublic)
262+
{
263+
var reprString = methodInfo.Invoke(co.inst, null) as string;
264+
return Runtime.PyString_FromString(reprString);
265+
}
266+
267+
//otherwise use the standard object.__repr__(inst)
268+
IntPtr args = Runtime.PyTuple_New(1);
269+
Runtime.PyTuple_SetItem(args, 0, ob);
270+
IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__");
271+
var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero);
272+
Runtime.XDecref(args);
273+
Runtime.XDecref(reprFunc);
274+
return output;
275+
}
276+
catch (Exception e)
277+
{
278+
if (e.InnerException != null)
279+
{
280+
e = e.InnerException;
281+
}
282+
Exceptions.SetError(e);
283+
return IntPtr.Zero;
284+
}
285+
}
286+
249287

250288
/// <summary>
251289
/// Standard dealloc implementation for instances of reflected types.

src/runtime/exceptions.cs

+23
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,29 @@ internal static Exception ToException(IntPtr ob)
3636
return e;
3737
}
3838

39+
/// <summary>
40+
/// Exception __repr__ implementation
41+
/// </summary>
42+
public new static IntPtr tp_repr(IntPtr ob)
43+
{
44+
Exception e = ToException(ob);
45+
if (e == null)
46+
{
47+
return Exceptions.RaiseTypeError("invalid object");
48+
}
49+
string name = e.GetType().Name;
50+
string message;
51+
if (e.Message != String.Empty)
52+
{
53+
message = String.Format("{0}('{1}')", name, e.Message);
54+
}
55+
else
56+
{
57+
message = String.Format("{0}()", name);
58+
}
59+
return Runtime.PyUnicode_FromString(message);
60+
}
61+
3962
/// <summary>
4063
/// Exception __str__ implementation
4164
/// </summary>

src/testing/Python.Test.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
<Compile Include="threadtest.cs" />
9292
<Compile Include="doctest.cs" />
9393
<Compile Include="subclasstest.cs" />
94+
<Compile Include="ReprTest.cs" />
9495
</ItemGroup>
9596
<ItemGroup>
9697
<Reference Include="Microsoft.CSharp" />
@@ -111,4 +112,4 @@
111112
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(SolutionDir)\src\tests\fixtures" />
112113
<!--Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" /-->
113114
</Target>
114-
</Project>
115+
</Project>

src/testing/ReprTest.cs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace Python.Test
5+
{
6+
/// <summary>
7+
/// Supports repr unit tests.
8+
/// </summary>
9+
public class ReprTest
10+
{
11+
public class Point
12+
{
13+
public Point(double x, double y)
14+
{
15+
X = x;
16+
Y = y;
17+
}
18+
19+
public double X { get; set; }
20+
public double Y { get; set; }
21+
22+
public override string ToString()
23+
{
24+
return base.ToString() + ": X=" + X.ToString() + ", Y=" + Y.ToString();
25+
}
26+
27+
public string __repr__()
28+
{
29+
return "Point(" + X.ToString() + "," + Y.ToString() + ")";
30+
}
31+
}
32+
33+
public class Foo
34+
{
35+
public string __repr__()
36+
{
37+
return "I implement __repr__() but not ToString()!";
38+
}
39+
}
40+
41+
public class Bar
42+
{
43+
public override string ToString()
44+
{
45+
return "I implement ToString() but not __repr__()!";
46+
}
47+
}
48+
49+
public class BazBase
50+
{
51+
public override string ToString()
52+
{
53+
return "Base class implementing ToString()!";
54+
}
55+
}
56+
57+
public class BazMiddle : BazBase
58+
{
59+
public override string ToString()
60+
{
61+
return "Middle class implementing ToString()!";
62+
}
63+
}
64+
65+
//implements ToString via BazMiddle
66+
public class Baz : BazMiddle
67+
{
68+
69+
}
70+
71+
public class Quux
72+
{
73+
public string ToString(string format)
74+
{
75+
return "I implement ToString() with an argument!";
76+
}
77+
}
78+
79+
public class QuuzBase
80+
{
81+
protected string __repr__()
82+
{
83+
return "I implement __repr__ but it isn't public!";
84+
}
85+
}
86+
87+
public class Quuz : QuuzBase
88+
{
89+
90+
}
91+
92+
public class Corge
93+
{
94+
public string __repr__(int i)
95+
{
96+
return "__repr__ implemention with input parameter!";
97+
}
98+
}
99+
100+
public class Grault
101+
{
102+
public int __repr__()
103+
{
104+
return "__repr__ implemention with wrong return type!".Length;
105+
}
106+
}
107+
}
108+
}

src/tests/test_exceptions.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,8 @@ def test_python_compat_of_managed_exceptions():
282282

283283
assert e.args == (msg,)
284284
assert isinstance(e.args, tuple)
285-
if PY3:
286-
strexp = "OverflowException('Simple message"
287-
assert repr(e)[:len(strexp)] == strexp
288-
elif PY2:
289-
assert repr(e) == "OverflowException(u'Simple message',)"
285+
strexp = "OverflowException('Simple message"
286+
assert repr(e)[:len(strexp)] == strexp
290287

291288

292289
def test_exception_is_instance_of_system_object():

src/tests/test_repr.py

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Test __repr__ output"""
4+
5+
import System
6+
import pytest
7+
from Python.Test import ReprTest
8+
9+
def test_basic():
10+
"""Test Point class which implements both ToString and __repr__ without inheritance"""
11+
ob = ReprTest.Point(1,2)
12+
# point implements ToString() and __repr__()
13+
assert ob.__repr__() == "Point(1,2)"
14+
assert str(ob) == "Python.Test.ReprTest+Point: X=1, Y=2"
15+
16+
def test_system_string():
17+
"""Test system string"""
18+
ob = System.String("hello")
19+
assert str(ob) == "hello"
20+
assert "<System.String object at " in ob.__repr__()
21+
22+
def test_str_only():
23+
"""Test class implementing ToString() but not __repr__()"""
24+
ob = ReprTest.Bar()
25+
assert str(ob) == "I implement ToString() but not __repr__()!"
26+
assert "<Python.Test.Bar object at " in ob.__repr__()
27+
28+
def test_hierarchy1():
29+
"""Test inheritance heirarchy with base & middle class implementing ToString"""
30+
ob1 = ReprTest.BazBase()
31+
assert str(ob1) == "Base class implementing ToString()!"
32+
assert "<Python.Test.BazBase object at " in ob1.__repr__()
33+
34+
ob2 = ReprTest.BazMiddle()
35+
assert str(ob2) == "Middle class implementing ToString()!"
36+
assert "<Python.Test.BazMiddle object at " in ob2.__repr__()
37+
38+
ob3 = ReprTest.Baz()
39+
assert str(ob3) == "Middle class implementing ToString()!"
40+
assert "<Python.Test.Baz object at " in ob3.__repr__()
41+
42+
def bad_tostring():
43+
"""Test ToString that can't be used by str()"""
44+
ob = ReprTest.Quux()
45+
assert str(ob) == "Python.Test.ReprTest+Quux"
46+
assert "<Python.Test.Quux object at " in ob.__repr__()
47+
48+
def bad_repr():
49+
"""Test incorrect implementation of repr"""
50+
ob1 = ReprTest.QuuzBase()
51+
assert str(ob1) == "Python.Test.ReprTest+QuuzBase"
52+
assert "<Python.Test.QuuzBase object at " in ob.__repr__()
53+
54+
ob2 = ReprTest.Quuz()
55+
assert str(ob2) == "Python.Test.ReprTest+Quuz"
56+
assert "<Python.Test.Quuz object at " in ob.__repr__()
57+
58+
ob3 = ReprTest.Corge()
59+
with pytest.raises(Exception):
60+
str(ob3)
61+
with pytest.raises(Exception):
62+
ob3.__repr__()
63+
64+
ob4 = ReprTest.Grault()
65+
with pytest.raises(Exception):
66+
str(ob4)
67+
with pytest.raises(Exception):
68+
ob4.__repr__()

src/tests/tests.pyproj

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<Compile Include="test_recursive_types.py" />
6060
<Compile Include="test_subclass.py" />
6161
<Compile Include="test_thread.py" />
62+
<Compile Include="test_repr.py" />
6263
<Compile Include="utils.py" />
6364
<Compile Include="fixtures\argv-fixture.py" />
6465
</ItemGroup>

0 commit comments

Comments
 (0)