Python List Comprehensions Help

That’s because you can’t use an else condition there (e.g. you can’t append a zero to the list with that syntax).

In which case, I suppose if it works, then it works, though I would probably add some parenthesis to make it clearer what you’re trying to do.

totals = [(a/b) if b else 0 for a,b in zip(net,eff)]

The difference is what the if..else statement is doing. In the first one, the comprehension looks at b, if it is not 0 it appends the result of (a/b), if it is 0 then it moves on to the next iteration. In the second one, the comprehension looks at b if it is not 0 it appends the result of (a/b), if it is 0 then it appends 0.

Throw this in script console to see the difference.

totals = [a/b for a,b in zip(net,eff) if b]

print totals

totals = []
for a,b in zip(net,eff):
    if b:
        totals.append(a/b)

print totals

totals = [(a/b) if b else 0 for a,b in zip(net,eff)]

print totals

totals = []

for a,b in zip(net,eff):
    if b:
        totals.append(a/b)
    else:
        totals.append(0)

To make it clearer what the difference between [x if a else y for x in iterable] and [x for x in iterable if a], i’d suggest putting it like this:

comprehension = [a/b for a, b in zip(net, eff) if b]

loop = []
for a, b in zip(net, eff):
    if b:
        loop.append(a/b)

comprehension == loop


comprehension = [(a/b) if b else 0 for a, b in zip(net, eff)]

loop = []
for a, b in zip(net, eff):
    loop.append(a/b if b else 0)

comprehension == loop

In the first case, the condition is a filter, it determines whether anything is appended.
In the second case, the condition is a ‘processing’, it determines what is appended.

Thanks
I used this:

def divideZero(a,b):
		return 0 if (a and b)==0 else a/b 

That’s… a weird way to do it.

Why are you comparing (a and b) to 0 ? In this case, it works, because dividing 0 returns 0.
But let’s check this step by step:

  • (a and b)
    This returns 1 if both a and b are non-zero.
  • (a and b) == 0
    Left expression compared to 0, which means the whole thing will return false if either a or b are 0.
  • 0 if (a and b)==0 else a/b
    if either a or b are 0, return 0, otherwise return a/b

It does what you think it does, but… probably not how you thought it did it.

you could write it like this

# you don't need to compare to 0, a and b will be evaluated as a boolean
return 0 if a and b else a/b
# checking only if b is 0
return 0 if b == 0 else a/b
# which can also be shortened, as 0 will evaluate to false
return a/b if b else 0
# or the way it was suggested earlier, though I don't quite like using try/catch for this
try:
    return a/b
except ZeroDivisionError:
    return 0

edit: To be clear, what I thought was weird is the (a and b) part.

Yeah, I had to think about all of that when I read it as well.

I would probably just avoid the inline if, were I going to go to the trouble of writing a function, and just write it like this:

def handleDivdeByZero(a,b):
    if b:
        return a/b
    return 0

Also, as a note, I don’t really like divideZero as a function name because it is too ambiguous as to what the function actually does. But that’s just me, as you were.

I used if (a and b) !=0 because it is easy for me to read.
I used the inline because it is the fastest format I read about, though faster as if a and b.
Don’t you have to address both when a is zero and b is zero, not just b?


This new problem hurts my head.

vals=system.dataset.toPyDataSet(value)
def handleDivideZero(a,b):
		return 0 if (a and b)==0 else a/b 
infeed= list(vals[0])[1::5]  
infeed= sum(infeed)
outfeed= list(vals[0])[2::5]
outfeed=sum(outfeed)
uptime=list(vals[0])[3::5]#*1.0/60
mV=list(vals[0])[5::5]	
rateXUptime=[a*b for a,b in zip(uptime,mV)]
rateXUptime=sum(rateXUptime)
uptime=sum(uptime)*1.0/60
downtime=list(vals[0])[4::5]
downtime=sum(downtime)*1.0/60	
duration=uptime+downtime		
avail=	handleDivideZero(float(uptime),float(duration))
perf= 	handleDivideZero(float(Infeed),rateXUptime)
qual=	handleDivideZero(Outfeed,float(Infeed))
value="{:.1%}".format(avail*perf*qual)
return value

I need eliminate all the values in lists when either the infeed or outfeed for a machine are less than 100 for that machine.

The parameters are paired in a groups of 5 columns per machine, so that is why I step by 5.
Got them from a historian binding that is property bound on this label where I calculate OEE.

Do I get the lists, then zip them together to do the excluding easier?
Is that like for sets of zips(a,b)?
Then when I have them zipped, make a new list excluding the ones when infeed and outfeed are less than 100?

Has to be a better way. Is there?

0/x is always 0, there's no error there. Which is also why your line works: you return 0 when a == 0, which is... not wrong.

There's a piece of code missing:

Thanks, edited it.

Why are you doing this division ?
You’re not using the result - at least not in the code you posted.

I will edit it more.

I didn’t think I needed all of it. Probably riddled with errors and typos now.
Probably I should have used the four zips, did the new lists, and gone that route.

I assume this is also supposed to be in a for loop? Otherwise, step by 5 doesn’t make any since.

I didn’t use any for loops.

It was working great. I have to remove machines that aren’t making over 100 on infeed or outfeed.
(Sometimes they get outfeed as they can drop in partials effectively.)

I usually don’t use enumerate for example though, so I thought that there would be a good way that I could learn to do the excluding.

Why not ?
I mean, [::5] will make a new list with every fifth item, which might be what he wants to do.
Speaking of what he wants to do… I have no idea, as usual :X

@zacharyw.larson , I think you need to be more explicit about what you’re trying to do ;p

I’m going home for today, I’ll check your issue tomorrow if it’s not solved yet.

I need to eliminate all the values in lists when either the infeed or outfeed for a machine are less than 100 for that machine.

The parameters are paired in groups of 5 columns per machine, so that is why I step by 5.
Got them from a historian binding that is property bound on this label where I calculate OEE.

Column 0 is the timestamp.
Columns [1::5] all infeeds.
Columns [5::5] all mVs.

I think for today I can just map them with four zips and I can probably figure it out.
I am not sure how to address them once I have zipped them, though I think that way would work.
There is probably some way though with enumerate that might be faster and simpler.

update

infeed2=	list(vals[0])[1::5]	#0 element is for timestamp, 5 params from each machine there after
outfeed2=	list(vals[0])[2::5]	
uptime2=	list(vals[0])[3::5]
downtime2=	list(vals[0])[4::5]
mV2=		list(vals[0])[5::5]	
bigList= zip(infeed2,outfeed2,uptime2,downtime2,mV2)
	
value=bigList

I did not realize it would be just one zip.
Probably easier to explain what I am trying to do if I can get it working in a clumsy way.

update

qual2=[a/b if (a<100 and b<100) for a,b,c,d,e in zip(infeed2,outfeed2,uptime2,downtime2,maxV2)]

This didn't work, parsing error. I am so bad at nested list comprehensions.

qual2=[[a[0]/a[1] if (a<100 and a<100) for a in b]    for b in bigList]

This did not work, parse error. I will go to the script console to work with lists in lists this afternoon. I think this was talked about in this thread before I re-reading it.

If you're using elements, you won't need the 'b' pat of the comprehension.

qual2=[a[0]/a[1] if (a[0]<100 and a[1]<100) for a in bigList]
infeed=[1000,2000,3000,0,5000,90]
outfeed=[1001,2001,0,4001,91,6001]
param3=[1.12,1.22,1.32,0,1.52,1.62]
param4=[3.1,3.2,3.3,3.4,3.5,3.6]
param5=[104,204,304,404,504,604]
bigList= zip(infeed, outfeed, 
param3,param4,param5)

print bigList
test=[a[0]/a[1] if (a[0]>100 and a[1]>100) for a in bigList]
print test

'#desired output [almost 1, almsot 1, ~100, ~1/100]'
#desired output [almost 1, almsot 1]

I got a no viable alternative after if.
Also, I had “less than” before, but I need “greater than”.

It also just occurred to me that it is easier to explain what I want to get in terms of desired output and in formatting that is used in the script console.

What is the expected output if either are not above 100?

Thanks for the sample values, btw. That helps. :slight_smile:

I want to omit the values when a value of infeed or outfeed are below 100, and I think I messed up my example haha.

#desired output [almost 1, almost 1]

Edited the post.

I would approach this differently.

Comprehensions aren’t always the answer, even when they can accomplish a task.

When I have a list of things that needs to be grouped I use this function (note that you can use the zip syntax directly, but I find a function name more readable):

def grouper(n,iterable):
    return zip(*[iter(iterable)] * n)

This will take a list of elements, group them to a length of n and return a list of tuples of those groups.

Then to accomplish what (I believe) your code is doing, I would write it like this:

def doCalcs():
 
	vals = system.dataset.toPyDataSet(value)
	
	sums = {'infeed':0,'outfeed':0,'uptime':0,'rateXUptime':0,'downtime':0}
	
	for infeed,outfeed,uptime,downtime,mV in grouper(5,vals[1:]):
		if infeed >= 100 and outfeed >= 100:
			sums['infeed'] += infeed
			sums['outfeed'] += outfeed
			sums['uptime'] += uptime / 60.0
			sums['rateXUptime'] += uptime * mV
			sums['doentime'] += downtime / 60.0
	
	duration = sums['uptime'] + sums['downtime']
	
	avail = sums['uptime']/duration if duration else 0
	perf = sums['infeed']/sums['rateXUptime'] if sums['rateXUptime'] else 0
	qual = sums['outfeed']/sums['infeed'] if sums['infeed'] else 0
	
	return "{:.1%}".format(avail*perf*qual)