Usually, the answer to "whichever to use, if
or try
", the answer is EAFP - it is easier to ask for forgiveness than permission, thus always prefer try
over if
. However, in this case, neither EAFP nor performance considerations shouldn't be reason to choose one over another. Instead you should choose the one that is correct.
Using isfile
because it makes your code prone to race conditions. Suppose someone removes the file just after you called isfile
and before you actually opened it - and you would get a spurious exception. However, there have been countless of security bugs because of code that first checks for existence, permissions, ownership or so on, of a file, and then open it - an attacker may change the file that the link is pointing to in between.
Furthermore, there are other reasons why open
would fail than just the file not existing:
>>> os.path.isfile('/etc/shadow')
True
>>> open('/etc/shadow')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
PermissionError: [Errno 13] Permission denied: '/etc/shadow'
isfile
also amounts to an extra system call and that in itself is more expensive than catching an exception; just doing the system call alone makes the overhead of catching an exception minuscule in comparison. As you have stated that you expect most of the file names to actually exist, the time spent in isfile
is just extra overhead.
I did some timings with Python 3 on Ubuntu 16.04, and os.path.isfile
for a non-existent file took /etc/foo
~2 µs (it is actually faster for an existing file /etc/passwd
, 1.8 µs); trying to open a non-existent file and failing and catching the IOError
took ~3 µs - so checking for the existence of a file using os.path.isfile
is faster than checking for its existence using open
; but that's not what your code needs to do: it needs to read the contents of a file that can be read, and return its contents, and for that, if 66 % of the files are expected to exist, then open
without checking is absolutely faster than using isfile
to check first, so this should be a no-brainer.
P.S: your code possibly leaks an open file on other Pythons than CPython, and is unnecessarily complicated. Using the with
block, this becomes much cleaner:
def read_path(path):
try:
with open(path, "rb") as f:
return f.read()
except IOError:
return None