Scoping in scripts

I’m driving myself crazy trying to determine why/how Ignition doesn’t follow Python scoping.

I have a variable defined that is not accessible by the functions. I have to pass it explicitly somehow.

I don’t understand why I have to set scheduledEvents=scheduledEvents and such. I should be able to read from and write to a globally scoped variable, no? The def is also in the for loop because it wouldn’t work outside of it. I’m not a python expert but the python tutorials seem to act like I shouldn’t have to reference global variables in the call at all. Isn’t that the point of being global?

In the example, newCnt is always prints 1 but is 0 at the end.

Example:

scheduledEvents = dataset[]
db = dataset2[]
newCnt = 0

for rowNum in range(db.getRowCount()):
   def addToSchedule(eventDict, scheduledEvents = scheduledEvents, updateCnt = updateCnt,newCnt=newCnt ):
          #do schedule stuff
          newCnt += 1
          print newCnt
          return scheduledEvents
   scheduledEvents = addToSchedule(eventDict,scheduledEvents)

Please advise me on what I am missing.

Thank you,
John
`

Where are you writing this script?

Unfortunately a notion of “legacy” scoping vs “standard” scoping developed over time. Which the script uses depends where it is.

On a scripting button.

I remember now, to edit closure variables (terminology?), you need to declare them with global in the function. Otherwise, it treats them as local-scoped variables.

Consider the following:

counter = 0

def addCounter():
	global counter
	counter += 1
	print counter

addCounter()
addCounter()
addCounter()

This will print:
>>1
>>2
>>3

https://www.programiz.com/python-programming/global-local-nonlocal-variables

Do you…actually need to define addToSchedule at all?

If you just do the schedule manipulation directly in the block of the for loop, there’s zero confusion about closures.

The loop could do one or two schedule changes depending on what the input has, so I thought it would be better to make it a def instead of writing the code twice.
And this is also about trying to finally understand how the scoping works since I’ve run into this a bit now and have just been avoiding the issue by explicitly calling it

Now it is saying its not Global
NameError: global name 'updateCnt' is not defined

I set updateCnt to 0 before the for loop starts.
Here’s the full function just in case I lied to you guys about something.

def addToSchedule(eventDict, scheduledEvents = scheduledEvents ):
	global updateCnt
	global newCnt
	#check for duplicates
	if eventDict["EventID"] in existingEvents:
		seRow = existingEvents.index(eventDict["EventID"])
		#if its already in, then just update it
		print "\t\tExisting event",eventDict["EventID"]," row ", seRow
		#if updating, get current percent complete
		eventDict["PctDone"] = scheduledEvents.getValueAt(seRow,'PctDone')
		db = system.dataset.updateRow(scheduledEvents,seRow,eventDict) 
		scheduledEvents = db
		updateCnt += 1
		print updateCnt
		#set back to 0 for next event
		eventDict["PctDone"] = 0
	else:
		#if its not, then add it
		print "\t\tadding new event",eventDict["EventID"]
		schEvent = [eventDict["EventID"],eventDict["ItemID"],eventDict["StartDate"],eventDict["EndDate"],eventDict["Label"],
					eventDict["Foreground"],eventDict["Background"],eventDict["LeadTime"],eventDict["LeadColor"],eventDict["PctDone"],
					eventDict["Item"],eventDict["Qty"],eventDict["Hrs"],eventDict["Push"],eventDict["CIP"],eventDict["GPH"],eventDict["CPH"],eventDict["WM Needed"],eventDict["Protein"]]
		#[ v for v in eventDict.values() ]
		print '\t\tscheduledEvents',scheduledEvents
		db = system.dataset.addRow(scheduledEvents,schEvent)
		print ""
		print '\t\tdb',db
		
		scheduledEvents = db
		newCnt += 1
		print newCnt
	return scheduledEvents
#end def

*edit on this, its a bit obnoxious that an UpdateRow requires a dictionary but an add row requires a list. Is there a better way to accomplish an Update or Add with the same data?

have you defined updateCnt above the def addToSchedule(...) function declaration?

errorCnt = 0
lineCnt = 0
updateCnt = 0
newCnt = 0
		
print 'Starting import on ',db.getRowCount(),'rows'
for rowNum in range(db.getRowCount()):```

Can you paste the whole code? My brain hurts jumping up and down trying to piece it all together

Sorry, there’s just a lot of unnecessary stuff in between I was trying to save you from.

lineCnt = 0
updateCnt = 0
newCnt = 0

print 'Starting import on ',db.getRowCount(),'rows'
for rowNum in range(db.getRowCount()):
	schEvent = []
	schDT = []
	
	cell1 = db.getValueAt(rowNum,"Line # ").strip()
	#print rowNum
	#print repr(cell1)

	if cell1!=u'':	
		#print 'Not blank'
		if cell1.find('Line') > -1:
			#LineNumCol = db.getValueAt(rowNum,"Line # ")
			LineNum = ""
			for char in cell1[cell1.find('Line')+5:]:
				if char.isnumeric():
					LineNum += char
				elif char.isspace() and LineNum == "":
					continue
				else:
					print 'Line', LineNum
					break
		else:
			LineNum = ""
	
	if LineNum != "" :
		#print 'Line',LineNum,"-" + str(db.getValueAt(rowNum,"Process Order"))+"-",db.getValueAt(rowNum,"Process Order").isnumeric()
		if (db.getValueAt(rowNum,"Process Order").replace(".0","")).isnumeric():
#				while currEventsInd < currEventsRowCount:
#					print currEvents.getValueAt(currEventsInd, "EventID"), "=",db.getValueAt(rowNum,"Process Order")
#					if currEvents.getValueAt(currEventsInd, "EventID") == db.getValueAt(rowNum,"Process Order"):
#						break
#					currEventsInd += 1				
			
			try:
				desc = str(db.getValueAt(rowNum,"Description ")).title()
				shortDesc = desc
				print '\t',shortDesc
				
				def addToSchedule(eventDict, scheduledEvents = scheduledEvents ):
					global updateCnt
					global newCnt
					#check for duplicates
					if eventDict["EventID"] in existingEvents:
						seRow = existingEvents.index(eventDict["EventID"])
						#if its already in, then just update it
						print "\t\tExisting event",eventDict["EventID"]," row ", seRow
						#if updating, get current percent complete
						eventDict["PctDone"] = scheduledEvents.getValueAt(seRow,'PctDone')
						db = system.dataset.updateRow(scheduledEvents,seRow,eventDict) 
						scheduledEvents = db
						updateCnt += 1
						print updateCnt
						#set back to 0 for next event
						eventDict["PctDone"] = 0
					else:
						#if its not, then add it
						print "\t\tadding new event",eventDict["EventID"]
						schEvent = [eventDict["EventID"],eventDict["ItemID"],eventDict["StartDate"],eventDict["EndDate"],eventDict["Label"],
									eventDict["Foreground"],eventDict["Background"],eventDict["LeadTime"],eventDict["LeadColor"],eventDict["PctDone"],
									eventDict["Item"],eventDict["Qty"],eventDict["Hrs"],eventDict["Push"],eventDict["CIP"],eventDict["GPH"],eventDict["CPH"],eventDict["WM Needed"],eventDict["Protein"]]
						#[ v for v in eventDict.values() ]
						print '\t\tscheduledEvents',scheduledEvents
						db = system.dataset.addRow(scheduledEvents,schEvent)
						print ""
						print '\t\tdb',db
						
						scheduledEvents = db
						newCnt += 1
						print newCnt
					return scheduledEvents
				#end def

				eventDict["EventID"] = db.getValueAt(rowNum,"Process Order")
				eventDict["ItemID"] = LineNum
				#print '\tStart',db.getValueAt(rowNum,"Start ")
				eventDict["StartDate"] = system.date.parse(db.getValueAt(rowNum,"Start "),'M/dd/yy HH:mm')
				eventDict["EndDate"] = system.date.parse(db.getValueAt(rowNum,"End "),'M/dd/yy HH:mm')
				eventDict["Label"] = shortDesc

				eventDict["Background"] = "color(214,255,198,255)"#Production light green

				eventDict["Item"] = db.getValueAt(rowNum,"Item")
				eventDict["Qty"] = db.getValueAt(rowNum,"Qty  ")
				eventDict["GPH"] = db.getValueAt(rowNum,"GPH")
				eventDict["CPH"] = db.getValueAt(rowNum,"CPH")
				eventDict["WM Needed"] = db.getValueAt(rowNum,"WM Needed")
				eventDict["Protein"] = db.getValueAt(rowNum,"Protein")
				eventDict["Hrs"] = db.getValueAt(rowNum,"Hrs. ")
				eventDict["Push"] = db.getValueAt(rowNum,"Push ")
				eventDict["CIP"] = db.getValueAt(rowNum,"CIP ")
				
				scheduledEvents = addToSchedule(eventDict,scheduledEvents)
				print '\tscheduledEvents',scheduledEvents
				
					
				if db.getValueAt(rowNum,"CIP ") != '':
					print '\tadding CIP'
					time = db.getValueAt(rowNum,"CIP ").split(":")
					hour = int(time[0])
					min = int(time[1])
					EndTime = system.date.addHours(system.date.addMinutes(system.date.parse(db.getValueAt(rowNum,"End "),'M/dd/yy HH:mm'),min-10),hour)
					
					eventDict["EventID"] = eventDict["EventID"] + " CIP"
					eventDict["StartDate"] = system.date.addMinutes(system.date.parse(db.getValueAt(rowNum,"End "),'M/dd/yy HH:mm'),5)
					eventDict["EndDate"] = EndTime
					eventDict["Label"] = "CIP"
					
					eventDict["Background"] = "color(107,151,232,135)"#CIP Dark Blue
					
					eventDict["Item"] = ""
					eventDict["Qty"] = ""
					eventDict["Hrs"] = ""
					eventDict["Push"] = ""
					eventDict["CIP"] = ""
					
					scheduledEvents = addToSchedule(eventDict,scheduledEvents)
					print '\tadding CIP'
					print '\tscheduledEvents',scheduledEvents
					
			except:
				errorCnt += 1
				print 'had error on ' + str(LineNum) + '\n' + str(traceback.format_exc())
				#logger.error(str(traceback.format_exc()))
			lineCnt += 1

It’s got to have something to do with the for loop or the fact that all of that is a in a function of its own since I am using system.util.invokeAsynchronous and system.util.invokeLater

Ah, that’s where the issue is, in calling it using invokeLater/Async. It works if called directly. This is one for @PGriffith

EDIT:
This works. You just need to define your variables in the same scope as you call the async/invokeLater function. To use it without declaring in this scope, you would need to use nonlocal instead of global, however Python 2.x doesn’t support nonlocal

updateCnt = 0

def h():
	def add():
		global updateCnt
		updateCnt += 1
		print updateCnt
		
	for i in range(2):
		add()
system.util.invokeLater(h)

Hmm, so the entire snippet you posted is inside a function definition that’s being invokeAsynced? Best guess is that invokeAsync is doing something weird, and ends up giving you Python <=2.5 scoping rules, instead of Python 2.7 scoping rules (which are basically the same as Python 3).

Either way, scoping in general with invokeAsync is distinctly non-intuitive.

So, basically, everything is screwed up and annoying because I am doing invokeAsync?

Try moving your whole block of code - everything you’re invokeAsyncing, into a project scoped library. That may clear up the scoping issues.

2 Likes

I will try that out first thing tomorrow. That sounds like a great idea to simplify all of this. It was a lot of a code to put on a button anyway.

2 Likes