IronLanguages / ironpython2

Implementation of the Python programming language for .NET Framework; built on top of the Dynamic Language Runtime (DLR).

Home Page:http://ironpython.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Calling a previous called library throws UnboundNameException

ChrisAcrobat opened this issue · comments

Prerequisites

The issue tracker is used to report bugs and request new features, NOT to ask questions.

Questions should be posted to the users mailing list which can be accessed at
https://ironpython.groups.io/g/users.

  • Are you running the latest version?
  • Are you reporting to the correct repository?
  • Did you perform a cursory search?

Description

We get IronPython.Runtime.UnboundNameException when we call a previous called library in a script.
LibA -> LibB -> LibC -> LibA -> UnboundNameException
Our python host implementation is base on this Stack Overflow response: https://stackoverflow.com/a/4644585
Un-commenting the # Workaround rows and import the lib before it is called fixes the issue, but does not scale very well.

Steps to Reproduce

# Test script
from LibStaticA import *
from LibInstanceA import *
testCalls = 10

print "Static test"
try:
	LibStaticA().Test(testCalls)
except Exception as ex:
	print ex.ToString().Split("\n")[0]

print "Instance test"
try:
	LibInstanceA().Test(testCalls)
except Exception as ex:
	print ex.ToString().Split("\n")[0]
# LibInstanceA
from LibInstanceB import *

class LibInstanceA:
	def Test(self, num):
	#	from LibInstanceA import * # Workaround
		LInstanceB().Test(num)
# LibInstanceB
from LibInstanceC import *

class LInstanceB:
	def Test(self, num):
	#	from LibInstanceC import * # Workaround
		LibInstanceC().Test(num)
# LibInstanceC
from LibInstanceA import *

class LibInstanceC:
	def Test(self, num):
		if 0 < num:
			print "LibInstanceC laps remaining: "+unicode(num)
		#	from LibInstanceA import * # Workaround
			LibInstanceA().Test(num-1)
		else:
			print "Done!"
# LibStaticA
from LibStaticB import *

class LibStaticA:
	@staticmethod
	def Test(num):
	#	from LibStaticB import * # Workaround
		LibStaticB.Test(num)
# LibStaticB
from LibStaticC import *

class LibStaticB:
	@staticmethod
	def Test(num):
	#	from LibStaticC import * # Workaround
		LibStaticC.Test(num)
# LibStaticC
from LibStaticA import *

class LibStaticC:
	@staticmethod
	def Test(num):
		if 0 < num:
			print "LibStaticC laps remaining: "+unicode(num)
		#	from LibStaticA import * # Workaround
			LibStaticA.Test(num-1)
		else:
			print "Done!"

Expected behavior: [What you expected to happen]

Static test
LibStaticC laps remaining: 10
LibStaticC laps remaining: 9
LibStaticC laps remaining: 8
LibStaticC laps remaining: 7
LibStaticC laps remaining: 6
LibStaticC laps remaining: 5
LibStaticC laps remaining: 4
LibStaticC laps remaining: 3
LibStaticC laps remaining: 2
LibStaticC laps remaining: 1
Done!
Instance test
LibInstanceC laps remaining: 10
LibInstanceC laps remaining: 9
LibInstanceC laps remaining: 8
LibInstanceC laps remaining: 7
LibInstanceC laps remaining: 6
LibInstanceC laps remaining: 5
LibInstanceC laps remaining: 4
LibInstanceC laps remaining: 3
LibInstanceC laps remaining: 2
LibInstanceC laps remaining: 1
Done!

Actual behavior: [What actually happened]

Static test
LibStaticC laps remaining: 10
IronPython.Runtime.UnboundNameException: global name 'LibStaticA' is not defined
Instance test
LibInstanceC laps remaining: 10
IronPython.Runtime.UnboundNameException: global name 'LibInstanceA' is not defined

Any idea on what could be the source of our problem? Is there any other information that I could try to provide that would help?

Versions

You can get this information from executing ipy -V.

Unable to execute, but has been tested on 2.7.12.

I believe the NameError (which shows up as UnboundNameException in C#) is the expected behavior. CPython results in exactly the same error.

Calling from LibStaticA import * will import what's in LibStaticA at the time of the call and not what would eventually be loaded. For example, when you run your main file, as soon as it hits from LibStaticA import * it executes LibStaticA which in turn loads LibStaticB and then LibStaticC, but when you do the from LibStaticA import * at the top of LibStaticC, there's nothing loaded in the LibStaticA module.

You can easily check this if you edit the top of LibStaticA:

# LibStaticA
A = 1
from LibStaticB import *
B = 2

You can use A in LibStaticC, but you will get an exception if you try to use B.

This type of circular dependency A->B->C->A is probably not recommended, but if it's really what you need then the only thing that comes to mind would be to not use from LibStaticA import * and go with something like import LibStaticA and later call it as LibStaticA.LibStaticA.Test(num-1).

Thanks for your suggestion, I'll respond again after we have tested it.

Added the following test:

# The test
from LibDirectStaticAFile import *
testCalls = 10

print "Direct test"
try:
	LibDirectStaticAFile.LibDirectStaticA.Test(testCalls)
except Exception as ex:
	print ex.ToString().Split("\n")[0]
# LibDirectStaticAFile
from LibDirectStaticBFile import *

class LibDirectStaticA:
	@staticmethod
	def Test(num):
		LibDirectStaticBFile.LibDirectStaticB.Test(num)
# LibDirectStaticBFile
from LibDirectStaticCFile import *

class LibDirectStaticB:
	@staticmethod
	def Test(num):
		LibDirectStaticCFile.LibDirectStaticC.Test(num)
# LibDirectStaticCFile
from LibDirectStaticAFile import *

class LibDirectStaticC:
	@staticmethod
	def Test(num):
		if 0 < num:
			print "LibDirectStaticC laps remaining: "+unicode(num)
			LibDirectStaticA.LibDirectStaticA.Test(num-1)
		else:
			print "Done!"

Got the following result:

Direct test
IronPython.Runtime.UnboundNameException: name 'LibDirectStaticAFile' is not defined

Note that it only says name and not global name as before. Could that be an error with our implementation?

PS: I have also renamed the other lib sources to *File. I mention it now in case reference them in the future but forget to mention it then.

Should be more like (difference is in the import):

# LibDirectStaticCFile
import LibDirectStaticCFile

class LibDirectStaticC:
	@staticmethod
	def Test(num):
		if 0 < num:
			print "LibDirectStaticC laps remaining: "+unicode(num)
			LibDirectStaticCFile.LibDirectStaticA.Test(num-1)
		else:
			print "Done!"

Ops, fixed that and now it works!
Thank you for you help! 🥇