Skip to content

Commit d914a89

Browse files
committed
Add function of passing an arbitrary .NET object as the value of an
attribute of PyObject by dynamic type
1 parent 6afc9e6 commit d914a89

File tree

5 files changed

+227
-2
lines changed

5 files changed

+227
-2
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Sean Freitag ([@cowboygneox](https://github.com/cowboygneox))
3030
- Serge Weinstock ([@sweinst](https://github.com/sweinst))
3131
- Virgil Dupras ([@hsoft](https://github.com/hsoft))
32+
- Wenguang Yang ([@yagweb](https://github.com/yagweb))
3233
- Xavier Dupré ([@sdpython](https://github.com/sdpython))
3334
- Zane Purvis ([@zanedp](https://github.com/zanedp))
3435
- ([@ArvidJB](https://github.com/ArvidJB))

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1414
- Added XML Documentation (#349)
1515
- Added Embedded tests on Appveyor (#353)
1616
- Added PY3 settings to configuration-manager (#346)
17+
- Added function of passing an arbitrary .NET object as the value of an attribute of PyObject (#370)(#373)
1718

1819
### Changed
1920

src/embed_tests/Python.EmbeddingTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
</ItemGroup>
7777
<ItemGroup>
7878
<Compile Include="pyinitialize.cs" />
79+
<Compile Include="dynamic.cs" />
7980
<Compile Include="pyimport.cs" />
8081
<Compile Include="pyiter.cs" />
8182
<Compile Include="pylong.cs" />

src/embed_tests/dynamic.cs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
using System;
2+
using System.Text;
3+
using NUnit.Framework;
4+
using Python.Runtime;
5+
6+
namespace Python.EmbeddingTest
7+
{
8+
[TestFixture]
9+
public class dynamicTest
10+
{
11+
private IntPtr gs;
12+
private outstream stream;
13+
14+
[SetUp]
15+
public void SetUp()
16+
{
17+
PythonEngine.Initialize();
18+
gs = PythonEngine.AcquireLock();
19+
20+
/* redirect sys.stdout to a .NET object */
21+
stream = new outstream();
22+
}
23+
24+
[TearDown]
25+
public void TearDown()
26+
{
27+
PythonEngine.ReleaseLock(gs);
28+
PythonEngine.Shutdown();
29+
}
30+
31+
/// <summary>
32+
/// Set the attribute of a pyobject to null.
33+
/// </summary>
34+
[Test]
35+
public void AssignNone()
36+
{
37+
dynamic sys = Py.Import("sys");
38+
sys.stderr = null;
39+
Assert.IsNull(sys.stderr);
40+
}
41+
42+
/// <summary>
43+
/// Set the attribute of a pyobject with a .NET object.
44+
/// </summary>
45+
[Test]
46+
public void AssignObject()
47+
{
48+
dynamic sys = Py.Import("sys");
49+
sys.stdout = stream;
50+
// Check whether there are the same object.
51+
Assert.AreEqual(sys.stdout, stream);
52+
53+
stream.clear();
54+
PythonEngine.RunSimpleString("print('Hello!')");
55+
Assert.AreEqual(stream.getvalue(), "Hello!\n");
56+
}
57+
58+
/// <summary>
59+
/// When the .NET object was created and used in Python side.
60+
/// </summary>
61+
/// <remarks>
62+
/// Needs Pull Request #376 in order for the exception below to show up.
63+
/// </remarks>
64+
[Test]
65+
[Ignore("System.ArgumentException : Cannot pass a GCHandle across AppDomains")]
66+
public void AssignObjectInPython()
67+
{
68+
PythonEngine.RunSimpleString(
69+
"import sys\n" +
70+
"from Python.EmbeddingTest import outstream\n" +
71+
"sys.stdout = outstream()\n"
72+
);
73+
dynamic sys = Py.Import("sys");
74+
dynamic obj = sys.stdout;
75+
Assert.IsTrue(obj is outstream);
76+
}
77+
78+
/// <summary>
79+
/// Check whether we can get the attr of a python object when the value of attr is a PyObject.
80+
/// </summary>
81+
[Test]
82+
public void AssignPyObject()
83+
{
84+
dynamic sys = Py.Import("sys");
85+
dynamic io = Py.Import("io");
86+
sys.stderr = io.StringIO();
87+
dynamic bb = sys.stderr; //Get the PyObject
88+
bb.write("Hello!");
89+
Assert.AreEqual(bb.getvalue().ToString(), "Hello!");
90+
}
91+
92+
/// <summary>
93+
/// Pass the .NET object in Python side.
94+
/// </summary>
95+
[Test]
96+
public void PassObjectInPython()
97+
{
98+
dynamic sys = Py.Import("sys");
99+
sys.stdout = stream;
100+
101+
//Pass the .NET object in Python side
102+
PythonEngine.RunSimpleString(
103+
"import sys\n" +
104+
"from io import StringIO\n" +
105+
"sys.stderr = sys.stdout\n"
106+
);
107+
108+
//Compare in Python
109+
stream.clear();
110+
PythonEngine.RunSimpleString(
111+
"import sys\n" +
112+
"print((sys.stderr is sys.stdout))\n"
113+
);
114+
Assert.AreEqual(stream.getvalue(), "True\n");
115+
116+
//Compare in .NET
117+
Assert.AreEqual(sys.stdout, sys.stderr);
118+
}
119+
120+
/// <summary>
121+
/// Pass the PyObject in .NET side
122+
/// </summary>
123+
[Test]
124+
public void PassPyObjectInNet()
125+
{
126+
dynamic sys = Py.Import("sys");
127+
sys.stdout = stream;
128+
sys.stderr = sys.stdout;
129+
130+
//Compare in Python
131+
PyObject res = PythonEngine.RunString(
132+
"import sys\n" +
133+
"print(sys.stderr is sys.stdout)\n"
134+
);
135+
Assert.AreEqual(sys.stdout.getvalue().ToString(), "False\n");
136+
137+
//Compare in .NET
138+
Assert.AreEqual(sys.stdout, sys.stderr);
139+
}
140+
}
141+
142+
/// <summary>
143+
/// Implement the interface of the sys.stdout redirection
144+
/// </summary>
145+
public class outstream
146+
{
147+
private StringBuilder buffer;
148+
149+
public outstream()
150+
{
151+
buffer = new StringBuilder();
152+
}
153+
154+
public void write(string str)
155+
{
156+
buffer.Append(str);
157+
}
158+
159+
public void flush()
160+
{
161+
}
162+
163+
public void close()
164+
{
165+
}
166+
167+
public void clear()
168+
{
169+
buffer.Clear();
170+
}
171+
172+
public string getvalue()
173+
{
174+
return buffer.ToString();
175+
}
176+
}
177+
}

src/runtime/pyobject.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,31 @@ public bool HasAttr(PyObject name)
183183
}
184184

185185

186+
/// <summary>
187+
/// GetAttr Method For Dynamic Type
188+
/// </summary>
189+
/// <remarks>
190+
/// Returns the named attribute of the Python object, or raises a
191+
/// PythonException if the attribute access fails.
192+
/// </remarks>
193+
public object GetAttrDynamic(string name)
194+
{
195+
IntPtr op = Runtime.PyObject_GetAttrString(obj, name);
196+
if (op == IntPtr.Zero)
197+
{
198+
throw new PythonException();
199+
}
200+
if (ManagedType.IsManagedType(op))
201+
{
202+
ManagedType managedMethod = ManagedType.GetManagedObject(op);
203+
if (managedMethod is CLRObject)
204+
{
205+
return ((CLRObject)managedMethod).inst;
206+
}
207+
}
208+
return CheckNone(new PyObject(op));
209+
}
210+
186211
/// <summary>
187212
/// GetAttr Method
188213
/// </summary>
@@ -275,6 +300,26 @@ public void SetAttr(string name, PyObject value)
275300
}
276301
}
277302

303+
public void SetAttrDynamic(string name, object value)
304+
{
305+
if (value == null)
306+
{
307+
int r = Runtime.PyObject_SetAttrString(obj, name, Runtime.PyNone);
308+
if (r < 0)
309+
{
310+
throw new PythonException();
311+
}
312+
}
313+
else if (value is PyObject)
314+
{
315+
this.SetAttr(name, (PyObject)value);
316+
}
317+
else
318+
{
319+
var ptr = Converter.ToPython(value, value.GetType());
320+
this.SetAttr(name, new PyObject(ptr));
321+
}
322+
}
278323

279324
/// <summary>
280325
/// SetAttr Method
@@ -891,7 +936,7 @@ public override bool TryGetMember(GetMemberBinder binder, out object result)
891936
{
892937
if (this.HasAttr(binder.Name))
893938
{
894-
result = CheckNone(this.GetAttr(binder.Name));
939+
result = this.GetAttrDynamic(binder.Name);
895940
return true;
896941
}
897942
else
@@ -904,7 +949,7 @@ public override bool TrySetMember(SetMemberBinder binder, object value)
904949
{
905950
if (this.HasAttr(binder.Name))
906951
{
907-
this.SetAttr(binder.Name, (PyObject)value);
952+
this.SetAttrDynamic(binder.Name, value);
908953
return true;
909954
}
910955
else

0 commit comments

Comments
 (0)