Python Dynamic Interop with Dotnet
Python
1 Articles
In this article, let's learn about how to use Python in .NET.
Table of Contents
- Introduction
- TLDR
- Why Dynamic Interop?
- Getting Started
- Getting Script Input from User
- Scripting Risks
- Executing Python Statements
- Interacting with Python Objects
- Passing Custom Dynamic Objects to Python
- Summary
Introduction
One of the benefits of the dynamic language runtime is the ability for our C# code to interoperate with other languages built on top of it such as IronPython. Whilst making use of something like IronPython is not an everyday occurrence for .NET developers. We're going to start off by looking at some of the reasons why we might want to consider dynamic language interop. We'll then jump into Visual Studio, and we'll see how we can get started with IronPython in our C# code. We'll then see how we can allow the end user to add scripting content that gets executed in our application. We'll also learn how we can execute Python statements in our C# application and get access to the result value of the statement. We'll learn that we can create Python objects and then access them dynamically from our C# code.
TLDR
Why Dynamic Interop?
Few reason why we might want to consider using dynamic interop is,
- Language features not existing in C#. For example, chained comparison operators “1 < age < 50”.
- Existing libraries and scripts don’t have .NET equivalents. For example, running python algorithm in C#.
- Add user scripts to application. For example, user entering business rules as python expressions.
Getting Started
Let's start off by seeing how we can execute simple Python expressions in C# code.
Steps
- Install IronPython Nuget Package.
- Add necessary using statements.
- Create Python ScriptEngine.
- Execute Python expression from the Python ScriptEngine.
- Get the result as dynamic result or a generic result from the Python Engine.
The following code sample shows how to execute simple python expressions in dotnet.
Code Sample - Python Interoperating - Simple Expression
Getting Script Input from User
We'll start off here by allowing the user to enter a Python expression that gets evaluated against a employee's age. The age will be stored in our C# code, but the expression will be entered at runtime by the user. So what we'll do here is add a variable to hold the employee's age. We're hard coding this age to keep the demo simple, but you can imagine this coming from a database or API
Code Sample - Python Interoperating - Getting Input from User
Steps
- Create Python ScriptEngine.
- Get the expression from user.
- Create a scope using ScriptEngine instance.
- Assign the Python variable token a to the ScriptEngine instance using SetVariable().
- Create a ScriptSource instance from engine using CreateScriptSourceFromString() and pass expression received from user and expression type as SourceCodeKind.Expression.
- Now execute the scope using script source.
Scripting Risks
One thing to bear in mind here is that allowing users to enter and execute arbitrary script or code may introduce security, data corruption, and other risks into the project. You should carefully consider these risks and put in measures to prevent them before adding this feature to your application. Next, we'll just ask the user to enter an expression and tell them to use the token a to represent the employee age. Now, rather than hard coding this expression, we're going to read it from the user. For demo purposes, we're not going to be adding validation to this input, but in a production app, you would want to do that. So, the token, a, is going to represent a Python variable.
Executing Python Statements
In addition to evaluating Python expressions, we can also execute Python statements and optionally access any variables that were set. Let's modify the code now to allow the user to enter a Python statement at runtime that gets passed to the Python engine and executed.
Code Sample - Python Interoperating - Single Statement
Steps
- Create Python ScriptEngine.
- Get the statement from user.
- Create a scope using ScriptEngine instance.
- Assign the Python variable token a to the ScriptEngine instance using SetVariable().
- Create a ScriptSource instance from engine using CreateScriptSourceFromString() and pass statement received from user and expression type as SourceCodeKind.SingleStatement.
- Now execute the scope using script source.
- Call the scope's GetVariable() method to get result.
So now that we're executing a statement, we're not getting the result back like we would when we evaluate an expression. so we are calling scope's GetVariable() method. Essentially here, we're saying have a look in the Python statement entered by the user, and look for a Python variable called result. This, of course, assumes that the user will set a result variable in their Python code.
Interacting with Python Objects
Let's see next at how we can interoperate with objects created in Python from our C# code.
Code Sample - Python Interoperating - Interacting with Python Objects
Steps
- Create Python ScriptEngine.
- Create Python Statement.
- Create a scope using ScriptEngine instance.
- Create a ScriptSource instance from engine using CreateScriptSourceFromString() and pass statement received from user and expression type as SourceCodeKind.SingleStatement.
- Now execute the scope using script source.
- Call the scope's GetVariable() method to get result.
Passing Custom Dynamic Objects to Python
Let's see next at how we can load Python code from an external file.
Code Sample - Python Interoperating - Interacting with External Python File
Steps
- Create Python ScriptEngine.
- Create C# dynamic element.
- Create a scope using ScriptEngine instance.
- Create a Python file and add it to project root and go to properties and select CopyIfNewer on BuildAction
- Assign the c# dynamic element to the ScriptEngine instance using SetVariable().
- Create a ScriptSource instance from engine using CreateScriptSourceFromFile() and pass python file name as parameter.
- Now execute the scope using script source.
Summary
In this article, we learned some of the reasons why we might want to consider using dynamic interop such as the ability to take advantage of language features not present in C# or to reuse existing Python code or libraries in our C# code. Next, we saw how we can get started with IronPython, and the first thing we went and did is installed the IronPython NuGet package. We saw how we can use the static CreateEngine method of the Python class to create an engine in which we can execute Python code. And we learned about a couple of the different types of source code that we can execute, the SourceCodeKind of Expression and the SourceCodeKind of SingleStatements. We learned how we can create an object in Python such as a Python tuple and then access this from our C# code and how we can execute some Python stored in a separate file by using the CreateScriptSourceFromFile method. We learned that we can pass an instance of our custom dynamic object into the Python engine and how this object can be manipulated in the Python script and thus access the resulting state in our C# code.