1

I have a list with string values that are changing asynchronously. I would like to continually check over the list until no item in the list contains a certain substring and then execute a command.

To make things less abstract I will explain my specific situation. I am using selenium to automate downloading several files, and after all the files have finished downloading I would like to close the webdriver. My current solution to the problem is the following:

while True:
    for file in os.listdir(download_dir):
        if 'tmp' in file.lower():
            break
    else:
        driver.quit()
        break

Basically, check if any element in the list (filenames in the download directory in this case) contains 'tmp' and if it does start checking again. If the whole list has been checked and there are no elements with 'tmp' in them quit the web driver and exit the loop.

This solution works fine, however in one of the stack overflow threads I used to come up with it (see here) many people said that, while something like this would work, it's probably a poor way to be doing it.

So my question is, is there a more pythonic way of doing this? Or if not just a better way in general? I don't have a problems with my solution, but some of the comments in the other answer give me a slight feeling that I should.

Community
  • 1
  • 1
kgoodrick
  • 391
  • 3
  • 12
  • 1
    Take a look at the [`watchdog` package](http://pythonhosted.org/watchdog/). – alecxe Dec 21 '15 at 05:35
  • @TomKarzes, the else is not intended to be paired with the if statement, it is paired with the for loop. See nmichaels answer to the linked question [here](http://stackoverflow.com/a/3704971/5693706). – kgoodrick Dec 21 '15 at 05:50
  • @alecxe, thanks for the suggestion. I think that would definitely work, but I'd like to do it without using another package if I can, especially if it's just for this one thing. – kgoodrick Dec 21 '15 at 05:53
  • Oops, my mistake, sorry. I removed my comment. – Tom Karzes Dec 21 '15 at 05:56
  • 1
    The question is an XYproblem. At least use [dircache](https://pymotw.com/2/dircache/). Also see [this SO QA](http://stackoverflow.com/questions/5738442/detect-file-change-without-polling) – Pynchia Dec 21 '15 at 06:07
  • @Pynchia I guess by XY problem you mean there should be a better way to check if the download is finished than by checking the directory for the file? I looked into that and I found [this](http://stackoverflow.com/questions/22714112/wait-for-download-to-finish-in-selenium-webdriver-java) question, and all the answers involved checking the directory, which is where I started with my current solution. dircache looks like a great idea though, even though it wouldn't change structure of the solution. – kgoodrick Dec 21 '15 at 06:32
  • Your code looks fine to me. If it does exactly what you want, you can ignore the comments and continue with it. – Terry Jan Reedy Dec 21 '15 at 07:06
  • Don't use "else" inside "for".(Try: "if" and "elif not" you will be see working nicely) – dsgdfg Dec 21 '15 at 07:40
  • 1
    The title of your question relates to one solution of the problem, not to the problem, i.e. it's an [XYproblem](http://xyproblem.info/) :) – Pynchia Dec 21 '15 at 07:56

3 Answers3

1

You can use the glob module if you can live with case-sensitive matching. If not, you can combime os.listdir and fnmatch.fnmatch to create an case-insensitive match:

import glob
import fnmatch
import os

download_dir = "/tmp"

def glob_insensitive(path, pattern):
    return fnmatch.filter(os.listdir(path), pattern)

while glob.glob( os.path.join(download_dir, "*tmp*") ):
    pass # or better sleep
driver.quit()

while glob_insensitive( download_dir, "*tmp*" ):
    pass # or better sleep
driver.quit()

For general style issues, I would use any with a generator expression:

while [ f for f in os.listdir(download_dir) if "tmp" in f.lower() ]:
    pass# or better sleep some time
driver.quit()

A generator expression would be better than constructing the list, but this cannot be easily tested if it is empty.

Jens
  • 9,058
  • 2
  • 26
  • 43
1

I might be wrong, but to me "pythonic" means simple to understand for anyone speaking python. Your piece of code is rather small, and almost anyone speaking python understands what it does.

However, the problem if your code is that it needs a paragraph of explanation of what is going on, and why you do it. Instead, you could write something self-explaining.

# we assume the file itself doesn't contain 'tmp'
is_ready = lambda filename: not ('tmp' in filename.lower())

while True:
    if all(is_ready(a_download) for a_download in os.listdir(download_dir)):
        driver.quit()
        break
newtover
  • 31,286
  • 11
  • 84
  • 89
-1

I would consider it to be more pythonic to avoid the while loop using a bit of itertools magic:

from itertools import repeat
from itertools import chain

for file in chain.from_iterable(repeat(os.listdir(download_dir))):
    if 'tmp' in file.lower():
        driver.quit()
        break

Basically, we are using repeat to create an iterator that just returns the listdir iterator forever. Now we have an infinite lazy list of iterators, an iterator on iterators basically. from_iterable then turns that iterator on iterators into a single chained iterator.

Since we only have one loop, we no longer have to worry about the double break problem and can greatly simplify the control logic.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72