310

What is the pythonic way of writing the following code?

extensions = ['.mp3','.avi']
file_name = 'test.mp3'

for extension in extensions:
    if file_name.endswith(extension):
        #do stuff

I have a vague memory that the explicit declaration of the for loop can be avoided and be written in the if condition. Is this true?

BiGYaN
  • 6,974
  • 5
  • 30
  • 43
TheMeaningfulEngineer
  • 15,679
  • 27
  • 85
  • 143
  • 5
    Though this question is well answered, perhaps the author originally thought of `if any((file_name.endswith(ext) for ext in extensions))`. – sapht Mar 02 '17 at 09:37

7 Answers7

630

Though not widely known, str.endswith also accepts a tuple. You don't need to loop.

>>> 'test.mp3'.endswith(('.mp3', '.avi'))
True
Gringo Suave
  • 29,931
  • 6
  • 88
  • 75
falsetru
  • 357,413
  • 63
  • 732
  • 636
  • 12
    do you know why it won't accept a list but does a tuple? just curious – ilyail3 Jul 13 '16 at 17:49
  • 2
    @falsetru The link in the answer does not explicitly answer that question. It only mentions that it *can* accept tuples, but not why it *cannot* accept lists. Since they are both sequences, the only difference I can potentially see is that lists are mutable, while tuples are immutable. I may be wrong, but I can't see any other reason why that is explicitly stated. – KymikoLoco Jan 31 '17 at 18:40
  • 6
    If you want to check if a string ends with a letter: `import string; str.endswith(tuple(string.ascii_lowercase))` – Alex Willison May 16 '17 at 13:43
  • 3
    just a note, `endswith` accepts tuple only for python 2.5 and above – Akash Singh Dec 31 '18 at 09:32
  • 2
    @ilyail3: I suspect the goal is to push people towards efficient constructs. 99% of the time, the suffixes to test are constant string literals. If you put them in a `list`, the CPython optimizer (not knowing `endswith` won't store/mutate them) has to rebuild the `list` on *every* call. Put them in a `tuple`, and the optimizer can store off the `tuple` at compile time and just load it from the array of constants cheaply on each call. Similar sort of nudge to the one you get from using `sum` on an iterable of strings; it would work either way, but the code would be slower the wrong way. – ShadowRanger Dec 08 '20 at 16:20
  • @falsetru my bad. deleted it to avoid confusion – Atlantis Jun 03 '22 at 22:41
63

Just use:

if file_name.endswith(tuple(extensions)):
Kenly
  • 24,317
  • 7
  • 44
  • 60
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
8

another way which can return the list of matching strings is

sample = "alexis has the control"
matched_strings = filter(sample.endswith, ["trol", "ol", "troll"])
print matched_strings
['trol', 'ol']
Akash Singh
  • 352
  • 4
  • 5
6

There is two ways: regular expressions and string (str) methods.

String methods are usually faster ( ~2x ).

import re, timeit
p = re.compile('.*(.mp3|.avi)$', re.IGNORECASE)
file_name = 'test.mp3'
print(bool(t.match(file_name))
%timeit bool(t.match(file_name)

792 ns ± 1.83 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

file_name = 'test.mp3'
extensions = ('.mp3','.avi')
print(file_name.lower().endswith(extensions))
%timeit file_name.lower().endswith(extensions)

274 ns ± 4.22 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Igor A
  • 311
  • 3
  • 10
2

I just came across this, while looking for something else.

I would recommend to go with the methods in the os package. This is because you can make it more general, compensating for any weird case.

You can do something like:

import os

the_file = 'aaaa/bbbb/ccc.ddd'

extensions_list = ['ddd', 'eee', 'fff']

if os.path.splitext(the_file)[-1] in extensions_list:
    # Do your thing.
Xxxo
  • 1,784
  • 1
  • 15
  • 24
1

I have this:

def has_extension(filename, extension):

    ext = "." + extension
    if filename.endswith(ext):
        return True
    else:
        return False
0

Another possibility could be to make use of IN statement:

extensions = ['.mp3','.avi']
file_name  = 'test.mp3'
if "." in file_name and file_name[file_name.rindex("."):] in extensions:
    print(True)
NeverHopeless
  • 11,077
  • 4
  • 35
  • 56