diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs
index 3556df0f6..4c9de1461 100644
--- a/src/embed_tests/TestDomainReload.cs
+++ b/src/embed_tests/TestDomainReload.cs
@@ -203,13 +203,13 @@ def test_obj_call():
// Create a new module
IntPtr module = PyRuntime.PyModule_New(name);
Assert.That(module != IntPtr.Zero);
- IntPtr globals = PyRuntime.PyObject_GetAttrString(module, "__dict__");
+ IntPtr globals = PyRuntime.PyObject_GetAttr(module, PyIdentifier.__dict__);
Assert.That(globals != IntPtr.Zero);
try
{
// import builtins
// module.__dict__[__builtins__] = builtins
- int res = PyRuntime.PyDict_SetItemString(globals, "__builtins__",
+ int res = PyRuntime.PyDict_SetItem(globals, PyIdentifier.__builtins__,
PyRuntime.PyEval_GetBuiltins());
PythonException.ThrowIfIsNotZero(res);
diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj
index d1e959196..4ca8140e9 100644
--- a/src/runtime/Python.Runtime.15.csproj
+++ b/src/runtime/Python.Runtime.15.csproj
@@ -143,6 +143,22 @@
+
+
+ TextTemplatingFileGenerator
+ intern_.cs
+
+
+
+
+
+
+ True
+ True
+ intern_.tt
+
+
+
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index a448e2bbd..a11a4b852 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -82,6 +82,8 @@
+
+
Properties\SharedAssemblyInfo.cs
@@ -89,7 +91,7 @@
-
+
@@ -131,7 +133,7 @@
-
+
@@ -185,4 +187,4 @@
-
+
diff --git a/src/runtime/classbase.cs b/src/runtime/classbase.cs
index 09adf5afe..a62e76050 100644
--- a/src/runtime/classbase.cs
+++ b/src/runtime/classbase.cs
@@ -279,7 +279,7 @@ public static IntPtr tp_repr(IntPtr ob)
IntPtr args = Runtime.PyTuple_New(1);
Runtime.XIncref(ob);
Runtime.PyTuple_SetItem(args, 0, ob);
- IntPtr reprFunc = Runtime.PyObject_GetAttrString(Runtime.PyBaseObjectType, "__repr__");
+ IntPtr reprFunc = Runtime.PyObject_GetAttr(Runtime.PyBaseObjectType, PyIdentifier.__repr__);
var output = Runtime.PyObject_Call(reprFunc, args, IntPtr.Zero);
Runtime.XDecref(args);
Runtime.XDecref(reprFunc);
diff --git a/src/runtime/classmanager.cs b/src/runtime/classmanager.cs
index 15f3d821d..26d0536ab 100644
--- a/src/runtime/classmanager.cs
+++ b/src/runtime/classmanager.cs
@@ -232,7 +232,7 @@ private static void InitClassBase(Type type, ClassBase impl)
var attr = (DocStringAttribute)attrs[0];
string docStr = attr.DocString;
doc = Runtime.PyString_FromString(docStr);
- Runtime.PyDict_SetItemString(dict, "__doc__", doc);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc);
Runtime.XDecref(doc);
}
@@ -249,8 +249,8 @@ private static void InitClassBase(Type type, ClassBase impl)
var ctors = new ConstructorBinding(type, tp, co.binder);
// ExtensionType types are untracked, so don't Incref() them.
// TODO: deprecate __overloads__ soon...
- Runtime.PyDict_SetItemString(dict, "__overloads__", ctors.pyHandle);
- Runtime.PyDict_SetItemString(dict, "Overloads", ctors.pyHandle);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__overloads__, ctors.pyHandle);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.Overloads, ctors.pyHandle);
ctors.DecrRefCount();
}
@@ -258,7 +258,7 @@ private static void InitClassBase(Type type, ClassBase impl)
if (!CLRModule._SuppressDocs && doc == IntPtr.Zero)
{
doc = co.GetDocString();
- Runtime.PyDict_SetItemString(dict, "__doc__", doc);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, doc);
Runtime.XDecref(doc);
}
}
diff --git a/src/runtime/exceptions.cs b/src/runtime/exceptions.cs
index 58506bfbb..ab28905d2 100644
--- a/src/runtime/exceptions.cs
+++ b/src/runtime/exceptions.cs
@@ -284,7 +284,7 @@ public static void SetError(Exception e)
}
IntPtr op = CLRObject.GetInstHandle(e);
- IntPtr etype = Runtime.PyObject_GetAttrString(op, "__class__");
+ IntPtr etype = Runtime.PyObject_GetAttr(op, PyIdentifier.__class__);
Runtime.PyErr_SetObject(new BorrowedReference(etype), new BorrowedReference(op));
Runtime.XDecref(etype);
Runtime.XDecref(op);
diff --git a/src/runtime/importhook.cs b/src/runtime/importhook.cs
index 8cf57c85d..e2795e4e3 100644
--- a/src/runtime/importhook.cs
+++ b/src/runtime/importhook.cs
@@ -43,11 +43,11 @@ static void InitImport()
// look in CLR modules, then if we don't find any call the default
// Python __import__.
IntPtr builtins = Runtime.GetBuiltins();
- py_import = Runtime.PyObject_GetAttrString(builtins, "__import__");
+ py_import = Runtime.PyObject_GetAttr(builtins, PyIdentifier.__import__);
PythonException.ThrowIfIsNull(py_import);
hook = new MethodWrapper(typeof(ImportHook), "__import__", "TernaryFunc");
- int res = Runtime.PyObject_SetAttrString(builtins, "__import__", hook.ptr);
+ int res = Runtime.PyObject_SetAttr(builtins, PyIdentifier.__import__, hook.ptr);
PythonException.ThrowIfIsNotZero(res);
Runtime.XDecref(builtins);
@@ -60,7 +60,7 @@ static void RestoreImport()
{
IntPtr builtins = Runtime.GetBuiltins();
- int res = Runtime.PyObject_SetAttrString(builtins, "__import__", py_import);
+ int res = Runtime.PyObject_SetAttr(builtins, PyIdentifier.__import__, py_import);
PythonException.ThrowIfIsNotZero(res);
Runtime.XDecref(py_import);
py_import = IntPtr.Zero;
diff --git a/src/runtime/intern.cs b/src/runtime/intern.cs
new file mode 100644
index 000000000..d8bdf863e
--- /dev/null
+++ b/src/runtime/intern.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Python.Runtime
+{
+ static partial class InternString
+ {
+ private static Dictionary _string2interns;
+ private static Dictionary _intern2strings;
+
+ static InternString()
+ {
+ var identifierNames = typeof(PyIdentifier).GetFields().Select(fi => fi.Name);
+ var validNames = new HashSet(identifierNames);
+ if (validNames.Count != _builtinNames.Length)
+ {
+ throw new InvalidOperationException("Identifiers args not matching");
+ }
+ foreach (var name in _builtinNames)
+ {
+ if (!validNames.Contains(name))
+ {
+ throw new InvalidOperationException($"{name} is not declared");
+ }
+ }
+ }
+
+ public static void Initialize()
+ {
+ _string2interns = new Dictionary();
+ _intern2strings = new Dictionary();
+
+ Type type = typeof(PyIdentifier);
+ foreach (string name in _builtinNames)
+ {
+ IntPtr op = Runtime.PyUnicode_InternFromString(name);
+ SetIntern(name, op);
+ type.GetField(name).SetValue(null, op);
+ }
+ }
+
+ public static void Shutdown()
+ {
+ foreach (var ptr in _intern2strings.Keys)
+ {
+ Runtime.XDecref(ptr);
+ }
+ _string2interns = null;
+ _intern2strings = null;
+ }
+
+ public static string GetManagedString(IntPtr op)
+ {
+ string s;
+ if (TryGetInterned(op, out s))
+ {
+ return s;
+ }
+ return Runtime.GetManagedString(op);
+ }
+
+ public static bool TryGetInterned(IntPtr op, out string s)
+ {
+ return _intern2strings.TryGetValue(op, out s);
+ }
+
+ private static void SetIntern(string s, IntPtr op)
+ {
+ _string2interns.Add(s, op);
+ _intern2strings.Add(op, s);
+ }
+ }
+}
diff --git a/src/runtime/intern_.cs b/src/runtime/intern_.cs
new file mode 100644
index 000000000..f9b3f43ec
--- /dev/null
+++ b/src/runtime/intern_.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace Python.Runtime
+{
+ static class PyIdentifier
+ {
+ public static IntPtr __name__;
+ public static IntPtr __dict__;
+ public static IntPtr __doc__;
+ public static IntPtr __class__;
+ public static IntPtr __module__;
+ public static IntPtr __file__;
+ public static IntPtr __slots__;
+ public static IntPtr __self__;
+ public static IntPtr __annotations__;
+ public static IntPtr __init__;
+ public static IntPtr __repr__;
+ public static IntPtr __import__;
+ public static IntPtr __builtins__;
+ public static IntPtr builtins;
+ public static IntPtr __overloads__;
+ public static IntPtr Overloads;
+ }
+
+
+ static partial class InternString
+ {
+ private static readonly string[] _builtinNames = new string[]
+ {
+ "__name__",
+ "__dict__",
+ "__doc__",
+ "__class__",
+ "__module__",
+ "__file__",
+ "__slots__",
+ "__self__",
+ "__annotations__",
+ "__init__",
+ "__repr__",
+ "__import__",
+ "__builtins__",
+ "builtins",
+ "__overloads__",
+ "Overloads",
+ };
+ }
+}
diff --git a/src/runtime/intern_.tt b/src/runtime/intern_.tt
new file mode 100644
index 000000000..c7142ec9f
--- /dev/null
+++ b/src/runtime/intern_.tt
@@ -0,0 +1,58 @@
+<#@ template debug="true" hostSpecific="true" #>
+<#@ output extension=".cs" #>
+<#
+ string[] internNames = new string[]
+ {
+ "__name__",
+ "__dict__",
+ "__doc__",
+ "__class__",
+ "__module__",
+ "__file__",
+ "__slots__",
+ "__self__",
+ "__annotations__",
+
+ "__init__",
+ "__repr__",
+ "__import__",
+ "__builtins__",
+
+ "builtins",
+
+ "__overloads__",
+ "Overloads",
+ };
+#>
+using System;
+
+namespace Python.Runtime
+{
+ static class PyIdentifier
+ {
+<#
+ foreach (var name in internNames)
+ {
+#>
+ public static IntPtr <#= name #>;
+<#
+ }
+#>
+ }
+
+
+ static partial class InternString
+ {
+ private static readonly string[] _builtinNames = new string[]
+ {
+<#
+ foreach (var name in internNames)
+ {
+#>
+ "<#= name #>",
+<#
+ }
+#>
+ };
+ }
+}
diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs
index f7afd5d6d..84abe28b9 100644
--- a/src/runtime/metatype.cs
+++ b/src/runtime/metatype.cs
@@ -109,7 +109,7 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
}
}
- IntPtr slots = Runtime.PyDict_GetItemString(dict, "__slots__");
+ IntPtr slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__);
if (slots != IntPtr.Zero)
{
return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__");
@@ -197,7 +197,7 @@ public static IntPtr tp_call(IntPtr tp, IntPtr args, IntPtr kw)
return IntPtr.Zero;
}
- var init = Runtime.PyObject_GetAttrString(obj, "__init__");
+ var init = Runtime.PyObject_GetAttr(obj, PyIdentifier.__init__);
Runtime.PyErr_Clear();
if (init != IntPtr.Zero)
diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs
index 011d8217d..7a10fcdef 100644
--- a/src/runtime/methodbinding.cs
+++ b/src/runtime/methodbinding.cs
@@ -84,7 +84,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
return IntPtr.Zero;
}
- string name = Runtime.GetManagedString(key);
+ string name = InternString.GetManagedString(key);
switch (name)
{
case "__doc__":
diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs
index eb3ce8a18..dc23e3ce5 100644
--- a/src/runtime/methodobject.cs
+++ b/src/runtime/methodobject.cs
@@ -133,8 +133,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
return Exceptions.RaiseTypeError("string expected");
}
- string name = Runtime.GetManagedString(key);
- if (name == "__doc__")
+ if (Runtime.PyUnicode_Compare(key, PyIdentifier.__doc__) == 0)
{
IntPtr doc = self.GetDocString();
Runtime.XIncref(doc);
diff --git a/src/runtime/moduleobject.cs b/src/runtime/moduleobject.cs
index 6313975da..c7085d18a 100644
--- a/src/runtime/moduleobject.cs
+++ b/src/runtime/moduleobject.cs
@@ -48,10 +48,10 @@ public ModuleObject(string name)
IntPtr pyfilename = Runtime.PyString_FromString(filename);
IntPtr pydocstring = Runtime.PyString_FromString(docstring);
IntPtr pycls = TypeManager.GetTypeHandle(GetType());
- Runtime.PyDict_SetItemString(dict, "__name__", pyname);
- Runtime.PyDict_SetItemString(dict, "__file__", pyfilename);
- Runtime.PyDict_SetItemString(dict, "__doc__", pydocstring);
- Runtime.PyDict_SetItemString(dict, "__class__", pycls);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__name__, pyname);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__file__, pyfilename);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__doc__, pydocstring);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__class__, pycls);
Runtime.XDecref(pyname);
Runtime.XDecref(pyfilename);
Runtime.XDecref(pydocstring);
@@ -282,7 +282,7 @@ public static IntPtr tp_getattro(IntPtr ob, IntPtr key)
return op;
}
- string name = Runtime.GetManagedString(key);
+ string name = InternString.GetManagedString(key);
if (name == "__dict__")
{
Runtime.XIncref(self.dict);
diff --git a/src/runtime/pyscope.cs b/src/runtime/pyscope.cs
index fee78b40a..20c933ad2 100644
--- a/src/runtime/pyscope.cs
+++ b/src/runtime/pyscope.cs
@@ -68,8 +68,8 @@ internal PyScope(IntPtr ptr, PyScopeManager manager)
variables = Runtime.PyModule_GetDict(obj);
PythonException.ThrowIfIsNull(variables);
- int res = Runtime.PyDict_SetItemString(
- variables, "__builtins__",
+ int res = Runtime.PyDict_SetItem(
+ variables, PyIdentifier.__builtins__,
Runtime.PyEval_GetBuiltins()
);
PythonException.ThrowIfIsNotZero(res);
diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs
index 1d688ef9a..df6cf7101 100644
--- a/src/runtime/pythonengine.cs
+++ b/src/runtime/pythonengine.cs
@@ -595,8 +595,8 @@ internal static PyObject RunString(string code, IntPtr? globals, IntPtr? locals,
if (globals == IntPtr.Zero)
{
globals = Runtime.PyDict_New();
- Runtime.PyDict_SetItemString(
- globals.Value, "__builtins__",
+ Runtime.PyDict_SetItem(
+ globals.Value, PyIdentifier.__builtins__,
Runtime.PyEval_GetBuiltins()
);
borrowedGlobals = false;
diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs
index 915e1db00..b9f471339 100644
--- a/src/runtime/runtime.cs
+++ b/src/runtime/runtime.cs
@@ -163,7 +163,7 @@ internal static void Initialize(bool initSigs = false, ShutdownMode mode = Shutd
MainManagedThreadId = Thread.CurrentThread.ManagedThreadId;
IsFinalizing = false;
-
+ InternString.Initialize();
GenericUtil.Reset();
PyScopeManager.Reset();
ClassManager.Reset();
@@ -237,7 +237,7 @@ private static void InitPyMembers()
// a wrapper_descriptor, even though dict.__setitem__ is.
//
// object.__init__ seems safe, though.
- op = PyObject_GetAttrString(PyBaseObjectType, "__init__");
+ op = PyObject_GetAttr(PyBaseObjectType, PyIdentifier.__init__);
SetPyMember(ref PyWrapperDescriptorType, PyObject_Type(op),
() => PyWrapperDescriptorType = IntPtr.Zero);
XDecref(op);
@@ -378,6 +378,7 @@ internal static void Shutdown(ShutdownMode mode)
Exceptions.Shutdown();
Finalizer.Shutdown();
+ InternString.Shutdown();
if (mode != ShutdownMode.Normal)
{
@@ -1598,6 +1599,12 @@ internal static IntPtr PyUnicode_FromString(string s)
return PyUnicode_FromUnicode(s, s.Length);
}
+ [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern IntPtr PyUnicode_InternFromString(string s);
+
+ [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
+ internal static extern int PyUnicode_Compare(IntPtr left, IntPtr right);
+
internal static string GetManagedString(in BorrowedReference borrowedReference)
=> GetManagedString(borrowedReference.DangerousGetAddress());
///
@@ -2183,7 +2190,7 @@ internal static void SetNoSiteFlag()
///
internal static IntPtr GetBuiltins()
{
- return PyImport_ImportModule("builtins");
+ return PyImport_Import(PyIdentifier.builtins);
}
}
diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs
index 78c34a027..43c160bc3 100644
--- a/src/runtime/typemanager.cs
+++ b/src/runtime/typemanager.cs
@@ -165,7 +165,7 @@ internal static IntPtr CreateType(Type impl)
IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict);
IntPtr mod = Runtime.PyString_FromString("CLR");
- Runtime.PyDict_SetItemString(dict, "__module__", mod);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod);
Runtime.XDecref(mod);
InitMethods(type, impl);
@@ -284,7 +284,7 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict);
string mn = clrType.Namespace ?? "";
IntPtr mod = Runtime.PyString_FromString(mn);
- Runtime.PyDict_SetItemString(dict, "__module__", mod);
+ Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod);
Runtime.XDecref(mod);
// Hide the gchandle of the implementation in a magic type slot.
@@ -564,7 +564,7 @@ internal static IntPtr BasicSubType(string name, IntPtr base_, Type impl)
IntPtr tp_dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict);
IntPtr mod = Runtime.PyString_FromString("CLR");
- Runtime.PyDict_SetItemString(tp_dict, "__module__", mod);
+ Runtime.PyDict_SetItem(tp_dict, PyIdentifier.__module__, mod);
return type;
}