Skip to content

Commit de227b5

Browse files
committed
Revamped RawPython.cs
1 parent eccc62a commit de227b5

3 files changed

Lines changed: 92 additions & 64 deletions

File tree

PythonNETExtensions/AsyncIO/AsyncIOCore.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ private static dynamic GenerateAwaiter(dynamic taskCompletionSource, dynamic cor
9292
TASK_COMPLETION_SOURCE_VAR_NAME = "tcs",
9393
EXCEPTION_VAR_NAME = "exception";
9494

95-
var codegen = (RawPython.CodeInterpolator)
95+
var awaiter = RawPython.Run<dynamic>(
9696
$"""
9797
async def {METHOD_NAME}():
9898
try:
@@ -103,9 +103,7 @@ private static dynamic GenerateAwaiter(dynamic taskCompletionSource, dynamic cor
103103
{(object) HandleAsyncExceptions:py}({TASK_COMPLETION_SOURCE_VAR_NAME}, {EXCEPTION_VAR_NAME});
104104
105105
return {METHOD_NAME}();
106-
""";
107-
108-
var awaiter = RawPython.Run<dynamic>(codegen);
106+
""");
109107

110108
return awaiter;
111109
}

PythonNETExtensions/Core/RawPython.cs

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Concurrent;
3-
using System.ComponentModel;
43
using System.Runtime.CompilerServices;
54
using System.Text;
65
using Python.Runtime;
@@ -10,6 +9,35 @@ namespace PythonNETExtensions.Core
109
{
1110
public static class RawPython
1211
{
12+
public enum CompilationOption
13+
{
14+
Auto,
15+
Compile,
16+
ExecuteOnly
17+
}
18+
19+
public interface IRunOptions
20+
{
21+
public static abstract CompilationOption CompilationOption { get; }
22+
public static abstract bool UseCachedScope { get; }
23+
}
24+
25+
public struct DefaultRunOptions: IRunOptions
26+
{
27+
public static CompilationOption CompilationOption => CompilationOption.Auto;
28+
public static bool UseCachedScope => false;
29+
}
30+
31+
public struct CachedScopeRunOptions: IRunOptions
32+
{
33+
public static CompilationOption CompilationOption => CompilationOption.Auto;
34+
35+
// Use new module, this is so that interpolated variables are "captured".
36+
public static bool UseCachedScope => true;
37+
}
38+
39+
private struct NoRet { }
40+
1341
private struct StringBuilderThreadStaticDefinition: IThreadStaticDefinition<StringBuilder>
1442
{
1543
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -38,7 +66,7 @@ public static void OnGet(ref PyModule item) { }
3866
}
3967

4068
[InterpolatedStringHandler]
41-
public struct CodeInterpolator
69+
public struct CodeInterpolator<OptsT> where OptsT: IRunOptions
4270
{
4371
private readonly StringBuilder LocalStringBuilder;
4472

@@ -47,16 +75,16 @@ public struct CodeInterpolator
4775
private int CurrentObjectIndex;
4876

4977
public bool ShouldCompile;
50-
78+
5179
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5280
public CodeInterpolator(int literalLength, int formattedCount)
5381
{
5482
// StringBuilderThreadStaticDefinition clears the StringBuilder for us
5583
var stringBuilder = LocalStringBuilder = ThreadStatic<StringBuilderThreadStaticDefinition, StringBuilder>.Item;
5684
stringBuilder.EnsureCapacity(literalLength);
5785

58-
Scope = ThreadStatic<PyScopeThreadStaticDefinition, PyModule>.Item;
59-
ShouldCompile = true;
86+
Scope = OptsT.UseCachedScope ? ThreadStatic<PyScopeThreadStaticDefinition, PyModule>.Item : Py.CreateScope("NoCache");
87+
ShouldCompile = OptsT.CompilationOption != CompilationOption.ExecuteOnly;
6088
}
6189

6290
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -69,7 +97,7 @@ public void AppendLiteral(string text)
6997
public void AppendFormatted(string text)
7098
{
7199
// If any of the formatted strings are not interned, we shouldn't compile
72-
if (string.IsInterned(text) == null)
100+
if (OptsT.CompilationOption == CompilationOption.Auto && string.IsInterned(text) == null)
73101
{
74102
ShouldCompile = false;
75103
}
@@ -112,64 +140,67 @@ public override string ToString()
112140
return LocalStringBuilder.ToString();
113141
}
114142
}
143+
144+
private static readonly ConcurrentDictionary<string, PyObject> CODE_TO_COMPILATION_MAP = new();
115145

116-
public enum CompilationOption
146+
private const string RET_VAR_NAME = "py_ret";
147+
148+
public static void Run(CodeInterpolator<DefaultRunOptions> code)
117149
{
118-
Auto,
119-
Compile,
120-
ExecuteOnly
150+
Run<NoRet, DefaultRunOptions>(code);
121151
}
122-
123-
private static readonly ConcurrentDictionary<string, PyObject> CODE_TO_COMPILATION_MAP = new();
124152

125-
public static void Run(CodeInterpolator code, CompilationOption compilationOption = CompilationOption.Auto)
153+
public static void RunWithCachedScope(CodeInterpolator<CachedScopeRunOptions> code)
126154
{
127-
var codeText = code.ToString();
128-
RunInternal(codeText, code, compilationOption);
155+
Run<NoRet, CachedScopeRunOptions>(code);
129156
}
130157

131-
public static RetT Run<RetT>(CodeInterpolator code, CompilationOption compilationOption = CompilationOption.Auto)
158+
public static RetT Run<RetT>(CodeInterpolator<DefaultRunOptions> code)
132159
{
133-
var codeText = code.ToString();
160+
return Run<RetT, DefaultRunOptions>(code);
161+
}
162+
163+
public static RetT RunWithCachedScope<RetT>(CodeInterpolator<CachedScopeRunOptions> code)
164+
{
165+
return Run<RetT, CachedScopeRunOptions>(code);
166+
}
167+
168+
public static RetT Run<RetT, OptsT>(CodeInterpolator<OptsT> code)
169+
where OptsT: IRunOptions
170+
{
171+
var hasRet = typeof(RetT) != typeof(NoRet);
134172

135-
const string METHOD_WRAPPER_NAME = "py_wrapper_method", RET_VAR_NAME = "py_ret";
173+
var codeText = code.ToString();
174+
175+
const string METHOD_WRAPPER_NAME = "py_wrapper_method";
136176

137177
// TODO: Somehow optimize performance of this
138-
codeText =
178+
codeText = hasRet ?
139179
$"""
140180
def {METHOD_WRAPPER_NAME}():
141181
{codeText.IndentCode()}
142182
143183
{RET_VAR_NAME} = {METHOD_WRAPPER_NAME}();
144-
""";
145-
146-
RunInternal(codeText, code, compilationOption);
184+
""" : codeText;
147185

148-
return code.Scope.Get<RetT>(RET_VAR_NAME);
149-
}
150-
151-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
152-
private static void RunInternal(string codeText, CodeInterpolator code, CompilationOption compilationOption)
153-
{
154186
var scope = code.Scope;
155187

156-
bool shouldCompile;
157-
158-
switch (compilationOption)
188+
var shouldCompile = code.ShouldCompile;
189+
190+
if (false)
159191
{
160-
case CompilationOption.Auto:
161-
shouldCompile = code.ShouldCompile;
162-
break;
163-
case CompilationOption.Compile:
164-
shouldCompile = true;
165-
break;
166-
case CompilationOption.ExecuteOnly:
167-
shouldCompile = false;
168-
break;
169-
default:
170-
throw new InvalidEnumArgumentException();
192+
Console.WriteLine(
193+
$"""
194+
Compiled: {shouldCompile}
195+
196+
Codegen:
197+
{codeText}
198+
199+
Scope: {scope}
200+
Variables: {scope.Variables().Keys()}
201+
""");
171202
}
172-
203+
173204
if (shouldCompile)
174205
{
175206
var compilations = CODE_TO_COMPILATION_MAP;
@@ -186,17 +217,15 @@ private static void RunInternal(string codeText, CodeInterpolator code, Compilat
186217
{
187218
scope.Exec(codeText);
188219
}
220+
221+
var ret = scope.Get<RetT>(RET_VAR_NAME);
189222

190-
if (false)
223+
if (OptsT.UseCachedScope)
191224
{
192-
Console.WriteLine(
193-
$"""
194-
Compiled: {shouldCompile}
195-
196-
Codegen:
197-
{codeText}
198-
""");
225+
scope.Variables().Clear();
199226
}
227+
228+
return ret;
200229
}
201230
}
202231
}

SampleCode/Program.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,16 @@ private static async Task Sample()
6161
var asyncIO = PythonExtensions.GetConcretePythonModule<AsyncIOModule>();
6262

6363
const int DELAY_SECONDS = 3;
64-
65-
var asyncCoroutine = RawPython.Run<dynamic>
66-
(
67-
$"""
68-
async def async_coroutine():
69-
print("{nameof(asyncIO)} is running!");
70-
await {asyncIO.Sleep(DELAY_SECONDS):py};
71-
return async_coroutine();
72-
"""
64+
65+
var asyncCoroutine = RawPython.Run<dynamic>
66+
(
67+
$"""
68+
async def async_coroutine():
69+
print("{nameof(asyncIO)} is running!");
70+
await {asyncIO.Sleep(DELAY_SECONDS):py};
71+
72+
return async_coroutine();
73+
"""
7374
);
7475

7576
var task = asyncIO.RunCoroutine(asyncCoroutine, handle);

0 commit comments

Comments
 (0)