Loop through perspective dict property in python script

Hi,
I have a problem when I want to loop through a dictionary or a list from the Perspective property in python script and it seems the return obj is not python dict/list.

How can I get perspective property as python dict or List?

When you use for with a dict like that, you’re iterating through its keys not its values.
I tend to explicitly ask what I want using:
dict1.keys()
or
dict1.values()
or
dict1.items()

Thus your script becomes:

dict1 = ...
for key, value in dict1.items():
   end = value['end']
   system.perspective.print(end)

More info:

2 Likes

By the way why the return dict object from perspective doesn’t have .copy() method?

Because it’s not a dict, it’s actually a

com.inductiveautomation.perspective.gateway.script.DotReferenceJythonMap

object which resembles a dict. However you can convert it to a dict to use copy:

dictObj = dict1.toDict().copy()

If you need to deepcopy though, you’ll need to import copy and use it from there

Thank you, Nick.
I try to import the copy module to use deepcopy() method to make iteration faster. (loop via objects with referencing to perspective property is very slow compare when we copy every thing first and then loop through)
The code work in the ignition script console but doesn't work in perspctive:

import copy
s = copy.deepcopy( dict( self.custom.Stack ) )

TypeError: com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper(): expected 40 args; got 0

What did I miss here?

1 Like

Hmm, not sure about that one… that’s one for @PGriffith. It’s also odd that the object returned when you read an ‘object’ type property of a component is different depending where you read it from:

If you don’t deepcopy, it works with copy. However just converting the object to a dict should effectively decouple if from the reference to the Perspective property which is really what you’re doing when you use copy/deepcopy on a dict.

The full error I get is:

Error running action 'dom.onClick' on _Testing/View@D/root/Label_6:
Traceback (most recent call last):
File "<function:runAction>", line 6, in runAction File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\copy.py", line 163, in deepcopy y = copier(x, memo) 
File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\copy.py", line 257, in _deepcopy_dict y[deepcopy(key, memo)] = deepcopy(value, memo)
File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\copy.py", line 190, in deepcopy y = _reconstruct(x, rv, 1, memo) 
File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\copy.py", line 329, in _reconstruct y = callable(*args)
File "C:\Program Files\Inductive Automation\Ignition\user-lib\pylib\copy_reg.py", line 93, in __newobj__ return cls.__new__(cls, *args) TypeError: com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper(): expected 40 args; got 0
1 Like

deepcopy is trying to recreate the original object type, which is not what you want. For a ‘normative’ deepcopy operation, you could borrow a trick from JS - encode to JSON and de-encode in a single step, which guarantees you’re dealing with ‘plain’ Python objects.

3 Likes

Ah, so dict is only converting the outer object to a dict, it's not converting the nested objects into dicts?

Right - dict is just the Python builtin, and all it does is go a ‘mapping-like object’ and repackage the key : value pairs within into a dict at the outermost layer; by design it doesn’t care if one of those values is itself a more complex object.

You could also write your own recursive function that calls dict or list as appropriate, which might be faster than the json encode/decode round trip, but the added complexity may not be worth it.

1 Like

I would be shocked if it wasn't worth it. Not to mention the effect on the types of the nested objects.