3
votes

We use IronPython in our open source project. I have problem accesing the variables added to the script scope like

private ScriptScope CreateScope(IDictionary<string, object> globals)
{
    globals.Add("starting", true);
    globals.Add("stopping", false);

    var scope = Engine.CreateScope(globals);
    scope.ImportModule("math");
    return scope;
}

https://github.com/AndersMalmgren/FreePIE/blob/master/FreePIE.Core/ScriptEngine/Python/PythonScriptEngine.cs#L267

I can use the globals from the main script, but any module that is loaded will fail. How can it be fixed?

update: Given this module mymodule.py

if starting: #starting is defined on the scope
   ...

From the main script executed using this code

void RunLoop(string script, ScriptScope scope)
{
    ExecuteSafe(() =>
    {
        var compiled = Engine.CreateScriptSourceFromString(script).Compile();

        while (!stopRequested)
        {
            usedPlugins.ForEach(p => p.DoBeforeNextExecute());
            CatchThreadAbortedException(() => compiled.Execute(scope));
            scope.SetVariable("starting", false);
            threadTimingFactory.Get().Wait();
        }
        scope.SetVariable("stopping", true);
        CatchThreadAbortedException(() => compiled.Execute(scope));
    });
}

https://github.com/AndersMalmgren/FreePIE/blob/master/FreePIE.Core/ScriptEngine/Python/PythonScriptEngine.cs#L163

from mymodule import * #this will load the moduel and it fails with

enter image description here

edit: In response to @BendEg's answer

I tried this

scope.SetVariable("__import__", new Func<CodeContext, string, PythonDictionary, PythonDictionary, PythonTuple, object>(ResolveImport));

ImportDelegate is not defined so tried using a Func instead, the ResolveImport method never triggers and I get the same exception that the name is not defined

edit: I changed the scope creation to

var scope = Engine.GetBuiltinModule();
globals.ForEach(g => scope.SetVariable(g.Key, g.Value));

Now the import delegate triggers but it crashes on first line with global name 'mouse' is not defined, mouse is not used from the module. It seems its confused when I add my custom globals to the BuiltinModule

2
Could you please provide a more complete example (e.g. what is executed against that scope and how it fails)?Simon Opelt

2 Answers

2
votes

This is probably not the right answer, as still doesn't allow to share variables defined in the main IronPhyton script with the imported modules, however is a step forward.

This approach allows to set variables at the Engine level rather then the script level and they will be available in every imported module.

engine = Python.CreateEngine();
engine.Runtime.Globals.SetVariable("test_global", "This is a test global variable.");

then, within any IronPhyton script is possible to access it using an import:

import test_global
print(test_global)

Unlike ScriptScope, which makes them directly available, those Global variables need to be imported.

Original article
https://ludovic.chabant.com/devblog/2009/12/24/exposing-global-variables-in-ironpython/

Disclaimer
I added this answer as I've been struggling to find any material on this topic other then this SO Q&A, so I'm posting this possible workaround to help future troubled readers (like myself)

1
votes

As far as i know, importing some module will create a new scope. So when creating an instance of PythonModule via from ... import ... they has it's own scope. In this new scope, your public variables are not available. Please correct me if i am wrong.

Workaround:

You could create some static class, which holdes the values. Than you can be sure, you always have them. For example:

namespace someNS
{
    public static class SomeClass
    {
        public static bool Start { get; set; }
    }
}

And than in your IP-Code:

from someNS import SomeClass

# Now you can access the member
yourVal = SomeClass.Start

maybe this is some thing you can use. You event don't need to set it as variable in the scope.

EDIT

Maybe this is working for you. In the code i override module importing and try to set the global vars:

First thing you need is, give IronPython some delegate, for module importing:

# Scope should be your default scope
scope.SetVariable("__import__", new ImportDelegate(ResolveImport));

Then override the import function:

private object ResolveImport(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple fromlist)
{
    // Do default import but set module
    var builtin = IronPython.Modules.Builtin.__import__(context, moduleName, globals, locals, fromlist, 0);
    context.ModuleContext.Module.__setattr__(context, "some_global", "Hello World");
    return builtin;
}

EDIT

Definition of ImportDelegate

delegate object ImportDelegate(CodeContext context, string moduleName, PythonDictionary globals, PythonDictionary locals, PythonTuple fromlist);