Python for .NET or .NET for Python
Denis Akhiyarov
Senior Consultant, Wood Group - Intelligent Operations
Python.NET core developer - @denfromufa (SO, github, twitter, linkedin)
Python, C#, Mathematica, VBA, Matlab, Pascal
Also checkout my talks on Pyomo and xlwings
from IPython.display import Image
Image('./tiobe_index.PNG')
Image('./so_trends.PNG')
Image('./redmonk_trends.PNG')
Image('./pypl_trends.PNG')
https://github.com/pythonnet/pythonnet
Python for .NET is a package that gives Python programmers nearly seamless integration with the .NET Common Language Runtime (CLR) and provides a powerful application scripting tool for .NET developers.
Runs on Windows (.NET Framework 4.0+), Linux and OSX (Mono 4.8+), Windows Subsystem for Linux (WSL), Python 2.7 to 3.6
What is really Python.NET?!
Image('./interop_bridge.PNG')
What is Python.NET good for?
Image('./pypi_pythonnet_downloads.PNG')
Image('./github_overview.PNG')
Image('./github_tags.PNG')
https://github.com/pythonnet/pythonnet/wiki/Projects-using-pythonnet
https://github.com/useaible/RyskampLearningMachine
Platform for AI faster than TF
https://github.com/pythonnet/pythonnet/wiki/Installation
pyinstaller PythonAppWithDotNet.py
SadConsole MonoGame app packaged into stand-alone executable:
Image('./nPython_console.PNG')
Mono embedded in CPython, running in Linux Subsystem on Windows 10 with X11 server
Image('./wsl_mono_pythonnet.PNG')
!python pythonnet\demo\wordpad.py
%cd pythonnet\demo
!python DynamicGrid.py
%cd ..\..\
Image('./pythonnet_solution.PNG')
!pythonnet\pythonnet.sln
!scriptcs
scriptcs (ctrl-c to exit or :help for help) >
Note that this package does not implement Python as a first-class CLR language - it does not produce managed code (IL) from Python code. Rather, it is an integration of the CPython engine with the .NET or Mono runtime. This approach allows you to use CLR services and continue to use existing Python code and C-API extensions while maintaining native execution speeds for Python code. If you are interested in a pure managed-code implementation of the Python language, you should check out the IronPython project, which is in active development.
Another project Pyjion allows to JIT compile Python bytecode to IL bytecode
A key goal for this project has been that Python for .NET should "work just the way you'd expect in Python", except for cases that are .NET specific (in which case the goal is to work "just the way you'd expect in C#"). In addition, with the IronPython project havin established a community, the goal is that code written for IronPython run without modification under Python for .NET.
A good way to start is to interactively explore .NET usage in python interpreter by following along with the examples in this tutorial. If you get stuck, there are also a number of demos and unit tests located in the source directory of the distribution that can be helpful as examples.
Python.NET treats CLR namespaces as Python packages.
import clr
from System import String
clr.AddReference('System.Collections')
from System.Collections import *
Types from any loaded assembly may be imported.
To load an assembly, use the AddReference
method:
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import Form
c:\python\python35_64b\lib\site-packages\ipykernel_launcher.py:2: DeprecationWarning: The module was found, but not in a referenced namespace. Implicit loading is deprecated. Please use clr.AddReference('System.Windows').
Python for .NET allows you to use any non-private from Python:
To create an instance of a managed class, you use the standard instantiation syntax, passing a set of arguments that match one of its public constructors:
from System.Drawing import Point
p = Point(5, 5)
In most cases, Python for .NET can determine the correct constructor to call automatically based on the arguments. In some cases, it may be necessary to call a particular overloaded constructor, which is supported by a special "Overloads" attribute, on a class:
from System import String, Char, Int32
s = String.Overloads[Char, Int32]('A', 10)
s = String.__overloads__[Char, Int32]('A', 10)
Python.NET
also supports generic types. A generic type must be bound to
create a concrete type before it can be instantiated. Generic types support
the subscript syntax to create bound types:
from System.Collections.Generic import Dictionary
from System import *
dict1 = Dictionary[String, String]()
dict2 = Dictionary[String, Int32]()
dict3 = Dictionary[String, Type]()
When you pass a list of types using the subscript syntax, you can also pass a subset of Python types that directly correspond to .NET types:
dict1 = Dictionary[str, str]()
dict2 = Dictionary[str, int]()
dict3 = Dictionary[str, Decimal]()
This shorthand also works when explicitly selecting generic methods or specific versions of overloaded methods and constructors (explained later).
You can also subclass managed classes in Python. See the helloform.py
file
in the /demo
directory of the distribution for a simple Windows Forms
example that demonstrates subclassing a managed class.
You can get and set fields and properties of CLR objects just as if they were regular attributes:
from System import Environment
name = Environment.MachineName
Environment.ExitCode = 1
If a managed object implements one or more indexers, you can call the indexer using standard Python indexing syntax:
from System.Collections import Hashtable
table = Hashtable()
table["key 1"] = "value 1"
Overloaded indexers are supported, using the same notation one would use in C#:
# Loading clrmagic extension (pip install clrmagic)
# to generate runtime C# code, usable from IPython
%reload_ext clrmagic
Below is runtime generated method idxnames
that shows example of overloading indexer in CLR type IndexedNames
%%CS idxnames System.dll
public static object idxnames() { return new IndexedNames(); }
class IndexedNames {
private string[] namelist = new string[size];
static public int size = 10;
public IndexedNames() { for (int i = 0; i < size; i++) namelist[i] = "N. A."; }
public string this[int index] {
get {
string tmp;
if ( index >= 0 && index <= size-1 ) tmp = namelist[index];
else tmp = "";
return ( tmp );
}
set { if( index >= 0 && index <= size-1 ) namelist[index] = value; }
}
public int this[string name] {
get {
int index = 0;
while(index < size) {
if (namelist[index] == name) return index;
index++;
}
return index;
}
}
}
<function clrmagic.create_cs_function.<locals>.<lambda>>
idx1=idxnames()
idx1[5]="abc"
idx1["abc"], idx1[5], idx1[0]
(5, 'abc', 'N. A.')
Methods of CLR objects behave generally like normal Python methods. Static methods may be called either through the class or through an instance of the class. All public and protected methods of CLR objects are accessible to Python:
from System import Environment
drives = Environment.GetLogicalDrives()
list(drives)
['C:\\', 'D:\\']
from System import Environment
print(Environment.GetFolderPath.__doc__)
help(Environment)
System.String GetFolderPath(SpecialFolder) System.String GetFolderPath(SpecialFolder, SpecialFolderOption) Help on class Environment in module System: class Environment(Object) | Void .ctor() | | Method resolution order: | Environment | Object | builtins.object | | Methods defined here: | | __call__(self, /, *args, **kwargs) | Call self as a function. | | __delitem__(self, key, /) | Delete self[key]. | | __eq__(self, value, /) | Return self==value. | | __ge__(self, value, /) | Return self>=value. | | __getitem__(self, key, /) | Return self[key]. | | __gt__(self, value, /) | Return self>value. | | __hash__(self, /) | Return hash(self). | | __init__(self, /, *args, **kwargs) | Initialize self. See help(type(self)) for accurate signature. | | __iter__(self, /) | Implement iter(self). | | __le__(self, value, /) | Return self<=value. | | __lt__(self, value, /) | Return self<value. | | __ne__(self, value, /) | Return self!=value. | | __new__(*args, **kwargs) from CLR.CLR Metatype | Create and return a new object. See help(type) for accurate signature. | | __setitem__(self, key, value, /) | Set self[key] to value. | | __str__(self, /) | Return str(self). | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | CommandLine = r'c:\python\python35_64b\python.exe -m ipykernel_l...\ke... | | CurrentDirectory = r'C:\Users\denis\Dropbox\pythonnet' | | CurrentManagedThreadId = 1 | | Exit = <unbound method 'Exit'> | Void Exit(Int32) | | ExitCode = 1 | | ExpandEnvironmentVariables = <unbound method 'ExpandEnvironmentVariabl... | System.String ExpandEnvironmentVariables(System.String) | | FailFast = <unbound method 'FailFast'> | Void FailFast(System.String) | Void FailFast(System.String, System.Exception) | | GetCommandLineArgs = <unbound method 'GetCommandLineArgs'> | System.String[] GetCommandLineArgs() | | GetEnvironmentVariable = <unbound method 'GetEnvironmentVariable'> | System.String GetEnvironmentVariable(System.String) | System.String GetEnvironmentVariable(System.String, System.EnvironmentVariableTarget) | | GetEnvironmentVariables = <unbound method 'GetEnvironmentVariables'> | System.Collections.IDictionary GetEnvironmentVariables() | System.Collections.IDictionary GetEnvironmentVariables(System.EnvironmentVariableTarget) | | GetFolderPath = <unbound method 'GetFolderPath'> | System.String GetFolderPath(SpecialFolder) | System.String GetFolderPath(SpecialFolder, SpecialFolderOption) | | GetLogicalDrives = <unbound method 'GetLogicalDrives'> | System.String[] GetLogicalDrives() | | HasShutdownStarted = False | | Is64BitOperatingSystem = True | | Is64BitProcess = True | | MachineName = 'DESKTOP-SOAE0FK' | | NewLine = '\r\n' | | OSVersion = <System.OperatingSystem object> | Void .ctor(System.PlatformID, System.Version) | | ProcessorCount = 4 | | SetEnvironmentVariable = <unbound method 'SetEnvironmentVariable'> | Void SetEnvironmentVariable(System.String, System.String) | Void SetEnvironmentVariable(System.String, System.String, System.EnvironmentVariableTarget) | | SpecialFolder = <class 'System.SpecialFolder'> | Void .ctor() | | SpecialFolderOption = <class 'System.SpecialFolderOption'> | Void .ctor() | | StackTrace = ' at System.Environment.GetStackTrace(Exception...ect.t... | | SystemDirectory = r'C:\WINDOWS\system32' | | SystemPageSize = 4096 | | TickCount = 2412656 | | UserDomainName = 'DESKTOP-SOAE0FK' | | UserInteractive = True | | UserName = 'denis' | | Version = <System.Version object> | Void .ctor() | Void .ctor(Int32, Int32) | Void .ctor(System.String) | Void .ctor(Int32, Int32, Int32) | Void .ctor(Int32, Int32, Int32, Int32) | | WorkingSet = 170029056 | | get_CommandLine = <unbound method 'get_CommandLine'> | System.String get_CommandLine() | | get_CurrentDirectory = <unbound method 'get_CurrentDirectory'> | System.String get_CurrentDirectory() | | get_CurrentManagedThreadId = <unbound method 'get_CurrentManagedThread... | Int32 get_CurrentManagedThreadId() | | get_ExitCode = <unbound method 'get_ExitCode'> | Int32 get_ExitCode() | | get_HasShutdownStarted = <unbound method 'get_HasShutdownStarted'> | Boolean get_HasShutdownStarted() | | get_Is64BitOperatingSystem = <unbound method 'get_Is64BitOperatingSyst... | Boolean get_Is64BitOperatingSystem() | | get_Is64BitProcess = <unbound method 'get_Is64BitProcess'> | Boolean get_Is64BitProcess() | | get_MachineName = <unbound method 'get_MachineName'> | System.String get_MachineName() | | get_NewLine = <unbound method 'get_NewLine'> | System.String get_NewLine() | | get_OSVersion = <unbound method 'get_OSVersion'> | System.OperatingSystem get_OSVersion() | | get_ProcessorCount = <unbound method 'get_ProcessorCount'> | Int32 get_ProcessorCount() | | get_StackTrace = <unbound method 'get_StackTrace'> | System.String get_StackTrace() | | get_SystemDirectory = <unbound method 'get_SystemDirectory'> | System.String get_SystemDirectory() | | get_SystemPageSize = <unbound method 'get_SystemPageSize'> | Int32 get_SystemPageSize() | | get_TickCount = <unbound method 'get_TickCount'> | Int32 get_TickCount() | | get_UserDomainName = <unbound method 'get_UserDomainName'> | System.String get_UserDomainName() | | get_UserInteractive = <unbound method 'get_UserInteractive'> | Boolean get_UserInteractive() | | get_UserName = <unbound method 'get_UserName'> | System.String get_UserName() | | get_Version = <unbound method 'get_Version'> | System.Version get_Version() | | get_WorkingSet = <unbound method 'get_WorkingSet'> | Int64 get_WorkingSet() | | set_CurrentDirectory = <unbound method 'set_CurrentDirectory'> | Void set_CurrentDirectory(System.String) | | set_ExitCode = <unbound method 'set_ExitCode'> | Void set_ExitCode(Int32) | | ---------------------------------------------------------------------- | Data descriptors inherited from Object: | | Overloads | | __overloads__ | | ---------------------------------------------------------------------- | Data and other attributes inherited from Object: | | Equals = <unbound method 'Equals'> | Boolean Equals(System.Object) | Boolean Equals(System.Object, System.Object) | | Finalize = <unbound method 'Finalize'> | Void Finalize() | | GetHashCode = <unbound method 'GetHashCode'> | Int32 GetHashCode() | | GetType = <unbound method 'GetType'> | System.Type GetType() | | MemberwiseClone = <unbound method 'MemberwiseClone'> | System.Object MemberwiseClone() | | ReferenceEquals = <unbound method 'ReferenceEquals'> | Boolean ReferenceEquals(System.Object, System.Object) | | ToString = <unbound method 'ToString'> | System.String ToString()
While Python for .NET will generally be able to figure out the right version of an overloaded method to call automatically, there are cases where it is desirable to select a particular method overload explicitly.
Methods of CLR objects have an __overloads__
, which will soon be
deprecated in favor of iPy compatible Overloads, attribute that can be
used for this purpose:
from System import Console
# in jupyter writes to nbserver console
Console.WriteLine.Overloads[bool](True)
Console.WriteLine.Overloads[str]("true")
Console.WriteLine.Overloads[int](42)
Similarly, generic methods may be bound at runtime using the subscript syntax directly on the method:
%%CS GenericMethods System.dll
public static object GenericMethods() {
return new TypeWithGenerics();
}
class TypeWithGenerics {
public TypeWithGenerics() {
}
public System.Type genericmethod<T>(T g) {
return g.GetType();
}
}
<function clrmagic.create_cs_function.<locals>.<lambda>>
twg = GenericMethods()
twg.genericmethod[int](10).Name, twg.genericmethod[str]("10").Name
('Int32', 'String')
You can raise and catch managed exceptions just the same as you would pure-Python exceptions:
from System import NullReferenceException
try:
raise NullReferenceException("aiieee!")
except NullReferenceException as e:
print(e.Message)
print(e.Source)
aiieee! None
The type System.Array
supports the subscript syntax in order to
make it easy to create managed arrays from Python:
from System import Array
myarray = Array[int]([1,2])
list(myarray)
[1, 2]
Managed arrays support the standard Python sequence protocols:
items = Array[int]([1,2])
# Get first item
v = items[0]
items[0] = v
print(v)
# Get last item
v = items[-1]
items[-1] = v
print(v)
# Get length
l = len(items)
print(l)
# Containment test
for test in items:
print(test)
1 2 2 1 2
Multidimensional arrays support indexing using the same notation one would use in C#:
items[0, 2]
items[0, 2, 3]
Managed arrays and managed objects that implement the IEnumerable interface can be iterated over using the standard iteration Python idioms:
import System
domain = System.AppDomain.CurrentDomain
for item in domain.GetAssemblies():
name = item.GetName()
print(name)
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 clrmodule, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null Python.Runtime, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 __CodeGenerator_Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null e__NativeCall_Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Data.SqlXml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Collections, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a Accessibility, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Windows, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a clrmagic, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null cysz1vk3, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null koewpyha, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
Using Microsoft-provided tools such as aximp.exe
and tlbimp.exe
,
it is possible to generate managed wrappers for COM libraries.
After generating such a wrapper, you can use the libraries from Python just like any other managed code.
Dynamic late-binding to COM objects requires some boiler-plate code due to special-handling in .NET
Type conversion under Python for .NET is fairly straightforward - most elemental Python types (string, int, long, etc.) convert automatically to compatible managed equivalents (String, Int32, etc.) and vice-versa. Note that all strings returned from the CLR are returned as unicode.
Types that do not have a logical equivalent in Python are exposed as instances of managed classes or structs (System.Decimal is an example).
The .NET architecture makes a distinction between value types
and
reference types
. Reference types are allocated on the heap, and value
types are allocated either on the stack or in-line within an object.
A process called boxing
is used in .NET to allow code to treat a value
type as if it were a reference type. Boxing causes a separate copy of the
value type object to be created on the heap, which then has reference
type semantics.
Understanding boxing and the distinction between value types and reference types can be important when using Python for .NET because the Python language has no value type semantics or syntax - in Python "everything is a reference".
Here is a simple example that demonstrates an issue. If you are an experienced C# programmer, you might write the following code:
items = System.Array.CreateInstance(Point, 3)
for i in range(3):
items[i] = Point(0, 0)
items[0].X = 1 # won't work!!
items[0].X
0
While the spelling of items[0].X = 1
is the same in C# and Python,
there is an important and subtle semantic difference.
In C# (and other compiled-to-IL languages), the compiler knows that
Point is a value type and can do the Right Thing here, changing the
value in place.
In Python however, "everything's a reference", and there is really no
spelling or semantic to allow it to do the right thing dynamically.
The specific reason that items[0]
itself doesn't change is that when
you say items[0]
, that getitem operation creates a Python object that
holds a reference to the object at items[0]
via a GCHandle.
That causes a ValueType (like Point) to be boxed, so the following
setattr (.X = 1
) changes the state of the boxed value,
not the original unboxed value.
The rule in Python is essentially:
the result of any attribute or item access is a boxed value
and that can be important in how you approach your code.
Because there are no value type semantics or syntax in Python, you may need to modify your approach. To revisit the previous example, we can ensure that the changes we want to make to an array item aren't "lost" by resetting an array member after making changes to it:
items = System.Array.CreateInstance(Point, 3)
for i in range(3):
items[i] = Point(0, 0)
# This _will_ work. We get 'item' as a boxed copy of the Point
# object actually stored in the array. After making our changes
# we re-set the array item to update the bits in the array.
item = items[0]
item.X = 1
items[0] = item
print(item, items[0], item.X, items[0].X)
{X=1,Y=0} {X=1,Y=0} 1 1
This is not unlike some of the cases you can find in C# where you have to
know about boxing behavior to avoid similar kinds of lost update
problems
(generally because an implicit boxing happened that was not taken
into account in the code).
This is the same thing, just the manifestation is a little different in Python. See the .NET documentation for more details on boxing and the differences between value types and reference types.
Note on SECURITY: because Python code running under Python for .NET is inherently unverifiable, it runs totally under the radar of the security infrastructure of the CLR so you should restrict use of the Python assembly to trusted code.
These classes include PyObject, PyList, PyDict, PyTuple, etc. You can review the nPython.exe source code in Console.csproj project for example of embedding CPython in console .NET app.
At a very high level, to embed Python in your application you will need to:
Python.Runtime.dll
in your build environmentPythonEngine.Initialize()
to initialize PythonPythonEngine.ImportModule(name)
or Py.Import(name)
to import a modulePy.GIL()
blocksImportant Note for embedders: Python is not free-threaded and uses a global interpreter lock to allow multi-threaded applications to interact safely with the Python interpreter. Much more information about this is available in the Python C-API documentation on the www.python.org Website.
When embedding Python in a managed application, you have to manage the GIL in just the same way you would when embedding Python in a C or C++ application.
Before interacting with any of the objects or APIs provided by the
Python.Runtime namespace, calling code must have acquired the Python
global interpreter lock by calling the PythonEngine.AcquireLock
method.
The only exception to this rule is the PythonEngine.Initialize
method,
which may be called at startup without having acquired the GIL.
When finished using Python APIs, managed code must call a corresponding
PythonEngine.ReleaseLock
to release the GIL and allow other threads
to use Python.
The AcquireLock and ReleaseLock methods are thin wrappers over the
unmanaged PyGILState_Ensure
and PyGILState_Release
functions from
the Python API, and the documentation for those APIs applies to
the managed versions.
%%CS numpynetcall Python.Runtime.dll;Microsoft.CSharp.dll;System.Core.dll;System.dll;System.Collections.dll;mscorlib.dll
public static object numpynetcall(string dummy) {
using (Python.Runtime.Py.GIL()) {
dynamic np = Python.Runtime.Py.Import("numpy");
dynamic sys = Python.Runtime.Py.Import("sys");
dynamic pp = Python.Runtime.Py.Import("pprint");
pp.pprint(np.cos(np.pi * 2));
dynamic sin = np.sin;
pp.pprint(sin(5));
double c = np.cos(5) + sin(5);
pp.pprint(c);
dynamic a = np.array(new System.Collections.Generic.List<float> { 1, 2, 3 });
pp.pprint(a.dtype);
dynamic b = np.array(new System.Collections.Generic.List<float> { 6, 5, 4 },
Python.Runtime.Py.kw("dtype", np.int32));
pp.pprint(b.dtype);
System.Console.WriteLine(a * b);
return (object)np;
}
}
<function clrmagic.create_cs_function.<locals>.<lambda>>
numpynetcall("dummy")
1.0 -0.95892427466313845 -0.6752620891999122 dtype('float64') dtype('int32')
<module 'numpy' from 'c:\\python\\python35_64b\\lib\\site-packages\\numpy\\__init__.py'>
%%CS sympynetcall Python.Runtime.dll;Microsoft.CSharp.dll;System.Core.dll;System.dll;System.Collections.dll;mscorlib.dll
public static object sympynetcall(string dummy) {
using (Python.Runtime.Py.GIL()) {
dynamic sym = Python.Runtime.Py.Import("sympy");
dynamic pp = Python.Runtime.Py.Import("pprint");
dynamic x = sym.symbols('x');
dynamic a = sym.symbols('a');
dynamic roots = sym.solvers.solve(sym.Pow(x, 2) - a, x);
pp.pprint(roots);
return (object)sym;
}
}
<function clrmagic.create_cs_function.<locals>.<lambda>>
sympynetcall("")
[-sqrt(a), sqrt(a)]
<module 'sympy' from 'c:\\python\\python35_64b\\lib\\site-packages\\sympy\\__init__.py'>
%%CS pynetcall Python.Runtime.dll;Microsoft.CSharp.dll;System.Core.dll
public static object pynetcall(string modstr)
{
dynamic modobj;
using (Python.Runtime.Py.GIL())
{
modobj = Python.Runtime.Py.Import(modstr);
}
return (object)modobj;
}
<function clrmagic.create_cs_function.<locals>.<lambda>>
pynetcall("sys").version
'3.5.3 |Anaconda custom (64-bit)| (default, Feb 22 2017, 21:28:42) [MSC v.1900 64 bit (AMD64)]'
Using Tensorflow Effectively with Python.NET
# RUN THIS IN SCRIPTCS
##r C:\Python\Python35_64b\lib\site-packages\Python.Runtime.dll
# using Python.Runtime;
# dynamic sys; using (Py.GIL()) {sys=Py.Import("sys");}
# dynamic np; using (Py.GIL()) {np=Py.Import("numpy");}
# dynamic tf; using (Py.GIL()) {tf=Py.Import("tensorflow");}
# dynamic data = np.ones(5);
# dynamic x=tf.constant(data, Py.kw("name","x"));
# dynamic z = tf.Variable(tf.multiply(tf.pow(x,2),5), Py.kw("name","z"));
# dynamic y = tf.Variable(tf.multiply(tf.constant(3.0,Py.kw("dtype","float64")),x), Py.kw("name", 'y'));
# dynamic model = tf.global_variables_initializer();
# dynamic ssn=tf.Session(); ssn.run(model); ssn.run(y).ToString();
https://github.com/pythonnet/pythonnet/wiki/Various-debugging-scenarios-of-embedded-CPython Mixed-mode cross-language debugging
Attach remote debugger (ptvsd) to Python code embedded with pythonnet: