Skip to content

Commit 7a41ef8

Browse files
committed
Add function of passing an arbitrary .NET object as the value of an
attribute of PyObject by dynamic type
1 parent 3995130 commit 7a41ef8

File tree

7 files changed

+234
-12
lines changed

7 files changed

+234
-12
lines changed

AUTHORS.md

+2
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))
@@ -39,3 +40,4 @@
3940
- ([@rico-chet](https://github.com/rico-chet))
4041
- ([@rmadsen-ks](https://github.com/rmadsen-ks))
4142
- ([@stonebig](https://github.com/stonebig))
43+
- Wenguang Yang ([@yagweb](https://github.com/yagweb))

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1313
- Added `pysetargv` (#347)
1414
- Added XML Documentation (#349)
1515
- Added Embedded tests on Appveyor (#353)
16-
- Added PY3 settings to configuration-manager (#346)
16+
-   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

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
33
<PropertyGroup>
44
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -30,7 +30,8 @@
3030
<DebugType>full</DebugType>
3131
</PropertyGroup>
3232
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseMono'">
33-
<DefineConstants Condition="'$(DefineConstants)' == ''"></DefineConstants>
33+
<DefineConstants Condition="'$(DefineConstants)' == ''">
34+
</DefineConstants>
3435
<Optimize>true</Optimize>
3536
<DebugType>pdbonly</DebugType>
3637
</PropertyGroup>
@@ -40,7 +41,8 @@
4041
<DebugType>full</DebugType>
4142
</PropertyGroup>
4243
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseWin'">
43-
<DefineConstants Condition="'$(DefineConstants)' == ''"></DefineConstants>
44+
<DefineConstants Condition="'$(DefineConstants)' == ''">
45+
</DefineConstants>
4446
<Optimize>true</Optimize>
4547
<DebugType>pdbonly</DebugType>
4648
</PropertyGroup>
@@ -50,7 +52,8 @@
5052
<DebugType>full</DebugType>
5153
</PropertyGroup>
5254
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseMonoPY3'">
53-
<DefineConstants Condition="'$(DefineConstants)' == ''"></DefineConstants>
55+
<DefineConstants Condition="'$(DefineConstants)' == ''">
56+
</DefineConstants>
5457
<Optimize>true</Optimize>
5558
<DebugType>pdbonly</DebugType>
5659
</PropertyGroup>
@@ -60,11 +63,13 @@
6063
<DebugType>full</DebugType>
6164
</PropertyGroup>
6265
<PropertyGroup Condition=" '$(Configuration)' == 'ReleaseWinPY3'">
63-
<DefineConstants Condition="'$(DefineConstants)' == ''"></DefineConstants>
66+
<DefineConstants Condition="'$(DefineConstants)' == ''">
67+
</DefineConstants>
6468
<Optimize>true</Optimize>
6569
<DebugType>pdbonly</DebugType>
6670
</PropertyGroup>
6771
<ItemGroup>
72+
<Reference Include="Microsoft.CSharp" />
6873
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
6974
<HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
7075
</Reference>
@@ -75,7 +80,8 @@
7580
<None Include="packages.config" />
7681
</ItemGroup>
7782
<ItemGroup>
78-
<Compile Include="InitializeTest.cs" />
83+
<Compile Include="dynamic.cs" />
84+
<Compile Include="pyinitialize.cs" />
7985
<Compile Include="pyimport.cs" />
8086
<Compile Include="pyiter.cs" />
8187
<Compile Include="pylong.cs" />
@@ -100,4 +106,4 @@
100106
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
101107
<Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" />
102108
</Target>
103-
</Project>
109+
</Project>

src/embed_tests/dynamic.cs

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
using NUnit.Framework;
2+
using Python.Runtime;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Text;
7+
8+
namespace Python.EmbeddingTest
9+
{
10+
[TestFixture]
11+
public class dynamicTest
12+
{
13+
private IntPtr gs;
14+
private outstream stream;
15+
16+
[SetUp]
17+
public void SetUp()
18+
{
19+
PythonEngine.Initialize();
20+
gs = PythonEngine.AcquireLock();
21+
22+
/* redirect sys.stdout to a .NET object */
23+
this.stream = new outstream();
24+
}
25+
26+
[TearDown]
27+
public void TearDown()
28+
{
29+
PythonEngine.ReleaseLock(gs);
30+
PythonEngine.Shutdown();
31+
}
32+
33+
/// <summary>
34+
/// Set the attribute of a pyobject to null.
35+
/// </summary>
36+
[Test]
37+
public void AssignNone()
38+
{
39+
dynamic sys = Py.Import("sys");
40+
sys.stderr = null;
41+
Assert.IsNull(sys.stderr);
42+
}
43+
44+
/// <summary>
45+
/// Set the attribute of a pyobject with a .NET object.
46+
/// </summary>
47+
[Test]
48+
public void AssignObject()
49+
{
50+
dynamic sys = Py.Import("sys");
51+
sys.stdout = this.stream;
52+
// Check whether there are the same object.
53+
Assert.AreEqual(sys.stdout, stream);
54+
55+
this.stream.clear();
56+
PythonEngine.RunSimpleString("print('Hello!')");
57+
Assert.AreEqual(stream.getvalue(), "Hello!\n");
58+
}
59+
60+
/// <summary>
61+
/// When the .NET object was created and used in Python side.
62+
/// </summary>
63+
[Test]
64+
//[Explicit]
65+
[Ignore]
66+
public void AssignObjectInPython()
67+
{
68+
PythonEngine.RunSimpleString(@"
69+
import sys
70+
from Python.EmbeddingTest import outstream
71+
sys.stdout = outstream()
72+
");
73+
dynamic sys = Py.Import("sys");
74+
var 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+
var 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 = this.stream;
100+
101+
//Pass the .NET object in Python side
102+
PythonEngine.RunSimpleString("import sys\n" +
103+
"from io import StringIO\n" +
104+
"sys.stderr = sys.stdout\n");
105+
106+
//Compare in Python
107+
this.stream.clear();
108+
PythonEngine.RunSimpleString("import sys\n" +
109+
"print((sys.stderr is sys.stdout))");
110+
Assert.AreEqual(this.stream.getvalue(), "True\n");
111+
112+
//compare in .NET
113+
Assert.AreEqual(sys.stdout, sys.stderr);
114+
}
115+
116+
/// <summary>
117+
/// Pass the PyObject in .NET side
118+
/// </summary>
119+
[Test]
120+
public void PassPyObjectInNet()
121+
{
122+
dynamic sys = Py.Import("sys");
123+
sys.stdout = this.stream;
124+
sys.stderr = sys.stdout;
125+
126+
//Compare in Python
127+
var res = PythonEngine.RunString("import sys\n" +
128+
"print(sys.stderr is sys.stdout)");
129+
Assert.AreEqual(sys.stdout.getvalue().ToString(), "False\n");
130+
131+
//compare in .NET
132+
Assert.AreEqual(sys.stdout, sys.stderr);
133+
}
134+
}
135+
136+
/// <summary>
137+
/// Implement the interface of the sys.stdout redirection
138+
/// </summary>
139+
public class outstream
140+
{
141+
public outstream()
142+
{
143+
this.buffer = new StringBuilder();
144+
}
145+
private StringBuilder buffer;
146+
public void write(String str)
147+
{
148+
this.buffer.Append(str);
149+
}
150+
public void flush() { }
151+
public void close() { }
152+
153+
public void clear()
154+
{
155+
this.buffer.Clear();
156+
}
157+
public string getvalue()
158+
{
159+
return this.buffer.ToString();
160+
}
161+
}
162+
}
File renamed without changes.

src/runtime/Python.Runtime.csproj

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
33
<PropertyGroup>
44
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -69,6 +69,12 @@
6969
<Optimize>false</Optimize>
7070
<DebugType>full</DebugType>
7171
</PropertyGroup>
72+
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWinPY3|x64'">
73+
<DefineConstants>TRACE;DEBUG;PYTHON3;PYTHON35;UCS2</DefineConstants>
74+
</PropertyGroup>
75+
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugMono|x64'">
76+
<DefineConstants>TRACE;DEBUG;PYTHON3;PYTHON35;UCS2</DefineConstants>
77+
</PropertyGroup>
7278
<ItemGroup>
7379
<Reference Include="System" />
7480
</ItemGroup>
@@ -158,4 +164,4 @@
158164
<Copy SourceFiles="$(TargetAssembly)" DestinationFolder="$(PythonBuildDir)" />
159165
<Copy SourceFiles="$(TargetAssemblyPdb)" Condition="Exists('$(TargetAssemblyPdb)')" DestinationFolder="$(PythonBuildDir)" />
160166
</Target>
161-
</Project>
167+
</Project>

src/runtime/pyobject.cs

+47-2
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 = CLRObject.GetInstHandle(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)