Run() or ops() object oddity - Windows 10, TD 2019.20140

Good chance I’m missing something here, but I’m seeing a strange behavior I can’t seem to work around.

@elburz and I were chatting this morning about delay scripts, and he ran into an issue that I couldn’t get out of my head this morning.

The expected behavior is to use ops() to return a list of operators that you then run a delay script against. That might look like this:

i = 0

for op in ops('item*'):

	script = "op('{}').par.Pulse.pulse()".format(op.path)
	print(script)
	
	printScript = "print(args[0])"

	i+=1

	run(printScript, script, delayFrames = i*60)
	run(script, delayFrames = i*60)

This however produces the following error message:

Traceback (most recent call last):
    File "</project1/text4:op('/project1/item1').par.Pulse.pulse()>", line 1
TypeError: 'td.containerCOMP' object is not callable

Printing the script, it does look as though it aught to work; and sure enough, running one of the printed lines does work as expected.

I tested this against a few other delay mechanisms, both of which work.

This script works correctly:

for i in range(7):
	script = "op('/project1/item{}').par.Pulse.pulse()".format(i+1)
	print(script)

	run(script, delayFrames = 60*i)

As does this one:

targetOps = [
	"/project1/item1",
	"/project1/item2",
	"/project1/item3",
	"/project1/item4",
	"/project1/item5",
	"/project1/item6",
	"/project1/item7"]

i = 0

for eachItem in targetOps:
	script = "op('{}').par.Pulse.pulse()".format(eachItem)
	print(script)

	i+=1

	run(script, delayFrames = 60*i)

Interestingly, the findChildren() method produces the same results as the ops method:

i = 0

for op in parent().findChildren(name='item*', depth=1):

	script = "op('{}').par.Pulse.pulse()".format(op.path)
	print(script)
	
	printScript = "print(args[0])"

	i+=1

	run(printScript, script, delayFrames = i*60)
	run(script, delayFrames = i*60)

Resulting error:

Traceback (most recent call last):
  File "</project1/text4:op('/project1/item1').par.Pulse.pulse()>", line 1
TypeError: 'td.containerCOMP' object is not callable

Finally, a list comprehension built from ops() is successful:

targetOps = [op.path for op in ops('item*')]

i = 0

for eachItem in targetOps:
	script = "op('{}').par.Pulse.pulse()".format(eachItem)
	print(script)

	i+=1

	run(script, delayFrames = 60*i)

This seems like it might be an issue with the ops() and findChildren() methods?

Attached is an example toe file:
ops-bug.toe (4.5 KB)

Thanks Derivative Team :slight_smile:

@raganmd you’re my hero for making this thread.

This was driving me crazy this morning as well. Was completely stumped why ops() wouldn’t work, even if I used ops() then grabbed the op.path and passed that in as well, the scripts would print out correctly but the td.containerCOMPs weren’t callable. Which is something I’ve never seen before. I’m running on latest TD stable + Win10.

Ok, the issue seems to be that you are using op as a variable: for op in ops(‘items*’), and that gets pushed onto the local dictionary for the run command. op in that case is a td.containerCOMP object, so you are essentially trying to do

op(‘project1/item7’)(‘/project1/item1’)

Hence the TypeError. Try assigning op = None before the 2 run scripts and you’lll get a NoneType is not callable error.

Stumped me for a bit too!

1 Like

Ahhhh! That makes perfect sense now that you say that @selina!

Thanks for another set of eyes here. :slight_smile:

for op in ops() used to be in our old Replicator callback until we discovered this. Perhaps it go into another callback somewhere?

@raganmd Did you write it from scratch or were you starting from a callbacks DAT?

I was talking with @elburz and using an example he had - which I bet was pulled form an old replicator example. It makes sense that this is a reserved term, I just totally blanked on that. Should have tried a different variable name - whoops :grimacing:

Could very have been pulled from old example. I think it’d be really pertinent if TouchDesigner had some warnings or errors that would help point to common issues like that, had too many of those with students that end up taking far too long to debug. I’m not sure how to implement that, it could be a linter running in TouchDesigner to warn if words like “op” are used, it could be a process that checks op names and forces you to not use some really common name space issues (such as naming a script requests and then trying to import requests lib), or it could even be as simple as a colour scheme that can be added into Sublime or VSCode that maybe colour codes things you should avoid doing in TouchDesigner, like using “op” in there.

Thank you for this Answer.
(sorry for pushing this “old” Post)

I was also trying different combos with the op as a variable, until i`ve found this post.

Problem:

Solution:

Just “clear” the variable with “del”.

Maybe someone else will struggle with a similar combo/idea.

Best wishes

Or just not use reserved keywords?
Just name the variable operator or maybe something more descriptive like parentOperator, selectedOperator etc…

1 Like

I recommend following @alphamoonbase 's advice. Re-using names from the TD global namespace will get you into all sorts of unexpected trouble.