Skip to content

Commit 4a9457f

Browse files
koubaafilmor
authored andcommitted
Provide hook to implement __repr__ (pythonnet#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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 38 additions & 0 deletions
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

Lines changed: 23 additions & 0 deletions
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

Lines changed: 2 additions & 1 deletion
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

Lines changed: 108 additions & 0 deletions
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

Lines changed: 2 additions & 5 deletions
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

Lines changed: 68 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)
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