Todd Lewden
Todd Lewden

Reputation: 309

Python 3 determine which part of "or" operation was true

I have a functioning piece of code that suits my purposes but I wasn't sure if there was a better way to meet the end I am looking for. Here is my code :

def eitherOne(image1, image2):
    curIm1 = pyautogui.locateOnScreen(image1)
    curIm2 = pyautogui.locateOnScreen(image2)
    while curIm1 == None or curIm2 == None:
        curIm1 = pyautogui.locateOnScreen(image1)
        curIm2 = pyautogui.locateOnScreen(image2)
    if curIm1 != None:
        x, y = pyautogui.center(curIm1)
        pyautogui.click(x,y)
    if curIm2 != None:
        x, y = pyautogui.center(curIm2)
        pyautogui.click(x,y)

What this is doing is looking for one of two images and then clicking on whichever ends up being true. Is there any method or function I can use that can determine which of the conditions around the "or" returned true without running a subsequent set of "if" operations? Even if the answer is "No, you need the if statements" I'd appreciate it so I am not going on a wild goose chase for no reason.

Thank you for your time!

Upvotes: 0

Views: 100

Answers (2)

wwii
wwii

Reputation: 23743

A v2.7 example of @tobias_k's comment to your question. In Boolean Operations, the value of the True espresion is returned.

class Img(object):
    def __init__(self, name):
        self.name = name
    def __bool__(self):
        return True

c1 = Img('c1')
c2 = None

current_image = c2 or c1
if current_image:
    print(current_image.name)
else:
    print('None')

In this example, c1 is assigned to current_image because it evaluates to True and its name is printed.

Upvotes: 1

chepner
chepner

Reputation: 530940

I'd probably do something like this. You create an infinite loop that repeatedly tries one image, then the other, breaking once one of them suceeds.

def eitherOne(image1, image2):
    images = itertools.cycle([image1, image2])
    for img in images:
        curIm = pyautogui.locateOnScreen(img)
        if curIm is not None:
            break
    x, y = pyautogui.center(curIm)
    pyautogui.click(x, y)

If you prefer a while loop to a for loop,

def eitherOne(image1, image2):
    images = itertools.cycle([image1, image2])
    curIm = None
    while curIm is None:
        curIm = pyautogui.locateOnScreen(next(images))
    x, y = pyautogui.center(curIm)
    pyautogui.click(x, y)

Or, you can do more with itertools to avoid an explicit loop altogether.

from itertools import cycle, imap, dropwhile

def eitherOne(image1, image2):
    curIm = next(dropwhile(lambda x: x is None, 
                           imap(pyautogui.locateOnScreen,
                                cycle([image1, image2]))))
    x, y = pyautogui.center(curIm)
    pyautogui.click(x, y)

(I'm mildly surprised you can't use None as the predicate to dropwhile, similar to how you can with filterfalse.)


Update: actually, since filter and map already return iterators in Python 3, there's no need to use dropwhile and imap:

def eitherOne(image1, image2):
    curIm = next(filter(None,
                        map(pyautogui.locateOnScreen,
                            cycle([image1, image2]))))
    x, y = pyautogui.center(curIm)
    pyautogui.click(x, y)

Bonus content!

To take the functional approach to an extreme, you could write the following in coconut:

def eitherOne(image1, image2) = ([images1, images2] |>
                                 cycle |> 
                                 map$(pyautogui.locateOnScreen) |>
                                 filter$(None) |>
                                 next |>
                                 pyautogui.center |*>
                                 pyautogui.click)

Upvotes: 3

Related Questions