views:

126

answers:

4

I was staring at a piece of Python code I produced, which, though correct, is ugly. Is there a more pythonic way of doing this?

    r = self.get_pixel(x,y, RED)
    g = self.get_pixel(x,y, GREEN)
    b = self.get_pixel(x,y, BLUE)
    t = function(r,g,b)
    if t: 
        r2, g2, b2 = t  
        self.set_pixel(x,y,RED, r2)
        self.set_pixel(x,y,GREEN, g2)
        self.set_pixel(x,y,BLUE, b2)

The problem is the repetition of the method calls for get_pixel and set_pixel. For your information:

    RED, GREEN, BLUE = range(3)

Also note that I'd like to preserve code clarity and cleanness.

+1  A: 

To call a function with different arguments and collect the results you can use a list comprehension:

r1, r2, r3 = [foo(x) for x in [x1, x2, x3]]

To call a function for its side-effects I'd recommend not using a list comprehension and instead using an ordinary for loop:

ijs = [(i1, j1), (i2, j2), (i3, j3)]
for i, j in ijs:
    bar(i, j)

However your problem really is not that you should not be calling your set pixel for each color separately. If at all possible, change your API so that you can do this as suggested by John Machin:

old_colors = self.get_pixel_colors(x, y)
new_colors = function(*old_colors)
if new_colors:
    self.set_pixel_colors(x, y, *new_colors)
Mark Byers
-1 This is merely adding ugly workarounds to an ugly API. It's API redesign time -- see the answer by @Muhammad Alkarouri, or mine.
John Machin
@John Machin: Sorry I should have mentioned that. My plan was to first give a specific answer to the exact question and then to handle the bigger picture and explain the alternative approaches. But while I was typing the first part others already beat me to the second part. I didn't just want to duplicate the exact same information that others already had posted in my post so I just left it as it was as no-one else answered the question. Now I see my answer was accepted and I can't delete my answer. So I have included your answer into my post. Is that OK? Or what do you suggest I should do?
Mark Byers
@Mark Byers: I would say by the pattern of votes that your referral to the other answer is working or not needed, so I don't think you should worry about it now. And thanks for mentioning me as "others who beat me":)
Muhammad Alkarouri
@Muhammad Alkarouri: I think the lesson I have learned is to complete my answer even if other people have already written the same thing as I was going to say.
Mark Byers
+1  A: 
colors = (RED, GREEN, BLUE)

r, g, b = [self.get_pixel(x, y, col) for col in colors]
t = function(r, g, b)
for col, rgb in zip(colors, t):
    self.set_pixel(x, y, col, rgb)
ars
+5  A: 

I would use a named tuple to represent the color, and change the class to use color attributes rather than individual get_pixel(x,y,c). For example:

from collections import namedtuple
Color = namedtuple('Color', 'red green blue')
#...

color = self.get_pixel(x, y)
t = function(*color)
if t:
    self.set_pixel(x, y, color)

Edit: thanks to John Machin for the corrections suggested here. His answer also gives more insight into the reasons for this approach. I would add that a namedtuple gives the advantage of having fields such as color.red, color.green, color.blue which I like to have available. YMMV.

Muhammad Alkarouri
This requires either changing the args of `function` (which may not be 'owned' by the OP), or writing `t = function(*color)`.
John Machin
@John Machin: quite right. I forgot the `*`, which is the reason I used a tuple in the first place. I have just edited the answer to correct it.
Muhammad Alkarouri
and the last line should be `self.set_pixel(x, y, color)`
John Machin
@John: serves me right for writing code without testing. I have corrected it again.
Muhammad Alkarouri
+6  A: 

As you are using self, it appears that get_pixel etc are methods of your class. Instead of list comprehensions and zip() and other workarounds, look at the APIs and fix them. Two suggestions:

  1. Write another method get_pixel_colors(x, y) which returns a 3-tuple. Then you can write r, g, b = self.get_pixel_colors(x, y)
  2. Similarly: self.set_pixel_colors(x, y, r, g, b)

Even better, you can use the *args notation:

old_colors = self.get_pixel_colors(x, y)
new_colors = function(*old_colors)
if new_colors:
    self.set_pixel_colors(x, y, *new_colors)
John Machin