Global objects in Perspective Scope

Oh okay, I didnt realize that the section that creates the singleton lock just executes when the project script is referenced, so it would ensure that it exists and I could access it that way.

Would there be any benefit to adding that content into the __enter__ and __exit__ methods on the singleton class so that I could just do
with MyClass as instance instead of having to get the lock and then the key? Or would it cause an issue if the gateway restarted and potentially the code inside the __enter__ and __exit__ methods had potentially changed?

No, those are instance methods and you can’t get the instance before you lock.

1 Like

@pturmel Thank you for your detailed explanation here. Can you confirm I am understanding this correctly:

  • The script containing the class definition stores a lock in globals if none exists.
  • The script containing the class definition instantiates the class and stores it in globals, “inheriting” values from the old instance if it exists in globals.
  • When using this class, for example in an event script, we should import the lock from the script containing the class definition, causing that script to run, replacing the class instance in globals. Then we can pull the class instance from globals and use it.

Yes, except you don’t import the lock. You just refer to it with a fully-qualified name–that is enough for Ignition to ensure it is imported. You should never import from any Ignition script library. Only import standard python modules (sys, traceback, etc.) and external stuff you’ve placed in pylib.

1 Like

Okay that makes sense. Thank you for your help.

Why should you never do this? I had an app that we needed to test the code with pytest, and so we ended up importing any modules we used as aliases instead of code.py. What this meant is that anything in a folder could not be fully qualified in the test

I.e: General.Logging needed to just be imported as Logging due to pytest not understanding the folder structure and the alias not allowing a package name.

Is there a technical reason why not to do this? Or just a “you shouldn’t need to do it in 99% of scenarios so just don’t ever do it”

There is a scenario (race condition) where an import in one script module could capture old code when multiple scripts were updated.

If you need a module to appear under another name, use assignment, not import, just before you use it. In function scope, don't rely on such from outer scopes. Only a reference to the fully-qualified name will ensure you get the latest code.

{ I wouldn't be surprised if jython 2.7 improved this situation, but I don't think it is gone. }

Edit: New bold above. The race has been caught, and a work-around developed:

1 Like

To clarify, by this do you mean:

Logging = General.Logging
myLogger = Logging.MyLogger()

vs

import General.Logging as Logging
myLogger = Logging.MyLogger()

Also, since python uses reference semantics instead of value semantics, even if I created the assignment at the beginning of the script instead of right before use, would it just be a reference to the currently loaded script at that time? And when the gateway scripts restarted due to any changes it would also force that reference to be replaced next time the scripts reloaded? I am curious why this part is necessary?

Yes.

But the common case should be myLogger = General.Logging.MyLogger()

Demonstrated empirically, some time ago. There's a race condition in there somewhere. Use fully-qualified references at time of use for elements in Ignition script libraries.

Just ran into the situation today on 8.1.17. It is definitely not gone.