Python List Comprehensions Help

That is a specific version, is there a particular reason?

I do have a running list of different priorities among the updates, but that one was not on my radar specifically
My top two reasons or motivations were for these
8.1.6 fixes sorting and now I know it adds the scheduling to the gateway scripts
8.1.11 and 8.1.12 fix bindings on multiple views

I am not sure what the deciding factors are, but I agree that updating will help.

Its just the latest stable version, unless a regression is found I like staying up to date as much as possible.

1 Like

Once a stable version is released, and it's just a minor version upgrade there really isn't much risk in just upgrading. Especially if you have a development server set up.

Why not work with the latest and greatest tools if you can? 8.1.0 is coming up on 2 years old at this point.

1 Like

I am used to using a “preferred” version of software and then just never ever changing.
I am the type of consumer the new XP operating system laptops are meant for haha.

The real initial pain point, that causes me to want to pick a preferred version, was trying to play table top games online with java based applications that everyone could run. In this case I want to update.

I think many drive thru banks and ATMs still use XP.

@lrose
It isn’t my choice on the updating. I would choose to update in this case. The larger the group using something, the less agility it has I think.

I am not sure if I worded it the best. I mean for the best.

I think when I used IQAN, I liked version 3.4
With RS Logix, they have a version they call the golden version

I understand that you may be restrained on upgrading, but this is like not upgrading from Windows XP to Windows XP Service Pack 3. (To use you're own example)

I have similar feelings when it comes to not upgrading Firmware versions for RSLogix. There is a reason the company released that minor change. Perhaps it doesn't effect you (yet), but it may. Also, I've dealt with the calls into any tech support for software, where question No. 1 is have you installed the latest patch?

There is definitely less freedom in the upgrade process with a larger group, but this is an extremely new platform in the world of software development and updates, and by extension upgrades, should be expected to be more common.

Any way, were moving away from the topic of this thread so, I'll just leave it there.

that is funny, I don’t upgrade to service pack 3 on XP because the plcs I connect won’t work with it

but agreed, off topic

It hadn't really completely hit me how much that helps me till today. I knew it was really helpful, but I didn't realize how much it helps till today.

Thanks so much.

Every single time I look at it, I learn another thing.

1 Like

Here’s a little line of code you can use when reading tags:

data = [tag.value for tag in system.tag.readBlocking(paths) if tag.quality.good]

This gets you directly a list of values, for each tag that has a good quality.
Obviously, don’t use this if you need to know why a tag’s quality was bad, or if you need the timestamp, etc… but it’s worth it to add as a little wrapper in your lib for when you’re testing things out

1 Like

I'm not sure I would recommend this. If a tag quality is not good, you can no longer line up the values in data to your tags in paths. Could be useful for specific use cases, but might be worth calling out the potential pitfall.

4 Likes

For me, that lining up the values is the best part of the share from Jordan, I have to line up so many.

I keep learning things from that post like the xrange thing, I didn’t know about that saving so much memory.

I appreciate this other tip too though.

When all you can are about are the values, to sum them up, average them, or whatever… having this a shortcut to do this is quite handy.

It’s obviously not suited for all situations, like processing each value then writing back to the original tags, but still.
Even then, you could modify it to include the tag path:

tags = [
	{
		'path': path,
		'value': tag.value
	} for path, tag in zip(paths, system.tag.readBlocking(paths)) if tag.quality.good
]

But it kinds of defeat the purpose, which was getting a simple array of values.

Edit: Also, it was a thread about list comprehensions, so… here’s one. or two. ;D

5 Likes

Completely fair. I just at least wanted to make sure there was a callout for the pitfall as it is not uncommon for visitors to see a post with code and do a quick copy/paste without really fully understanding the code. Especially since this one can lead to some nasty bugs down the road as it could work fine for retrieving values based on position during testing/production and then fail at a random time in the future when a tag quality is not good (or worse, work without error but grab a value it shouldn’t).

You’re saying you can save me time saying:

numMachines = 18
baseNetTags= '[Test]M{0:02d}/M{0:02d}ProductionData/M{0:02d}Net'
pathsGross = [baseNetTags.format(i) for i in xrange(1,numMachines + 1)]	
valuesIn=system.tag.readBlocking(pathsGross)
valueOut=[]
for x,y in zip(pathsNet,valuesIn):
	if 'Good' in str(y.quality):	 
	 valueOut.append(y.value)
print sum(valueOut)

by using this code instead?

numMachines = 18
baseNetTags='[Test]M{0:02d}/M{0:02d}ProductionData/M{0:02d}Net'
pathsNet = [baseNetTags.format(i) for i in xrange(1,numMachines + 1)]	
netTotal = sum([i.value for i in system.tag.readBlocking(pathsNet) if i.quality.good])
print netTotal

the data and the valuesOut would both have a 13th value because the values do not line up to the 18 machines in either case, I think

How does the i.quality.good work?
I think I understood when it was written like if 'Good" in y
How is that part of the list known to be quality?

I think the former code was great for teaching me, and this shorter code is good for this specific function.

Questions:

  1. I can still use system.tag.read, so what is happening when I use it?
    I didn’t understand the part about here:
    7.9 to 8.1 Upgrade Guide - Ignition User Manual 8.1 - Ignition Documentation

  2. how is the “quality” usable in i.quality.good in the above posts?
    When I print the list, I get the values, but the list also has names of each element or something?
    I don’t know how this is referred to so I am not sure how to look it up.

You can go one step further in this case, and remove the brackets in

netTotal = sum([i.value for i in system.tag.readBlocking(pathsNet) if i.quality.good])

sum works just fine with generator expressions, so

netTotal = sum(i.value for i in system.tag.readBlocking(pathsNet) if i.quality.good)

Will work on the expression without having to create the list.

The quality attribute of qualified values has an isGood() method that returns true if the quality is good. As @pturmel taught me yesterday, there's some kind of netbeans black magic that allows you to use .good instead of .isGood(), and that is apparently an optimization.
It's exactly the same thing as using tag.value instead of tag.getValue() or tag.quality instead of tag.getQuality().
You can use .isGood() if you find the meaning to be clearer (I do). I find it conveys its meaning best when written as tag.quality.isGood().

I'm not sure I understand, so let's go back to the code snippet:

[tag.value for tag in system.tag.readBlocking(paths)]

This will iterate through the list of tags returned by readBlocking, get the value of each tag, and make a list out of them. You can add a condition to a comprehension, using this syntax:

[element for element in list if condition]

In your case, if you want the values of only the qualified values that have a good quality, then that what you check:

[qval.value for qval in qval_list if 'Good' in str(qval.quality)]

which can be simplified with

[qval.value for qval in qval_list if qval.quality.isGood()]

Edit:
Wait, I think I got it. No, the resulting list won't have the names of the tags. But the qualified values are still available WHILE iterating through them. If you 'unroll' the comprehension, it might be a bit clearer:

for tag in system.tag.readBlocking(paths):
    if tag.quality.isGood():
        values.append(tag.value)

If you want your list to retain the paths, use the other snippet I posted:

tags = [
	{
		'path': path,
		'value': tag.value
	} for path, tag in zip(paths, system.tag.readBlocking(paths)) if tag.quality.good
]

This will return a list of dictionaries containing two keys, one for the path, and the other for the value.

1 Like

This is great information

what about the system.tag.read()?

Questions:

  1. I can still use system.tag.read, so what is happening when I use it?
    I didn’t understand the part about here:
    7.9 to 8.1 Upgrade Guide - Ignition User Manual 8.1 - Ignition Documentation

While they still work, for now, they’re not being maintained, and there’s no guarantee they’ll even be there in 6 months. So save yourself some trouble and just use the new ones.
Why are they being deprecated ? Maybe their internal code needed some overhaul, I don’t know. But I find they also carry meaning a bit more clearly - there’s no doubt now that readBlocking is not asynchronous. I agree that having to pass lists is kind of a pain, but you can quite easily make your own wrappers that get rid of that:

def readTagBlocking(path):
    return system.tag.readBlocking([path])[0]

def writeTagBlocking(path, value):
    return system.tag.writeBlocking([path], [value])[0]

def readTagAsync(path, callback):
    system.tag.readAsync([path], callback)

def writeTagAsync(path, value, callback):
    system.tag.writeAsync([path], [value], callback)

what if I have two different sets of base tags?
Like I can do an exception list.
something like:

exceptions =[2,4,5,8,9,10,11,12]
nonExceptions = [3,6,14,15,16,17,18]
counterS='NetSCounter'
counterA='NetACounter'
baseTag ='[Test]M{0:02d}/M{0:02d}ProductionData/M{0:02d}{counter}'


pathsNewNet=[ baseNetTags.format(i,i,i,k) for i in xrange(1,numMachines + 1) ]	



well that isn’t right

I don’t think I understand where you’re getting at…

I think I have a situation where I need two for clauses.

I have 18 machines.
Half of them need to use tags from s-counters and half from a-counters.

So I have to iterate through the 18 machines for my paths
I also have to set the counter type in the path for each.

I am looking in:

https://www.geeksforgeeks.org/string-formatting-in-python/
https://www.w3schools.com/python/python_lists_comprehension.asp
https://www.pythonforbeginners.com/basics/list-comprehensions-in-python

trying to stitch together a proper list comprehension
I saw on stackexchange someone recommended two for clauses, but then their example was hard to follow