Natalia
Natalia

Reputation: 385

Python formatting % where int is from the set

I have such line:

if something in ["name_1", "name_2", "name_3", "name_4", "name_5"]

and I wonder if I can write it shorter using formatting. I know that I can write it like that:

if something in ["name_%d" %(1), "name_%d" %(2), "name_%d" %(3)]:

but because those strings have the same part "name_" I hoped that there is maybe shorter way of formatting, similar to this one (this doesn't work):

if something in ["name_%d" %(1, 2, 3, 4, 5)]:

Any ideas?

Upvotes: 0

Views: 45

Answers (3)

Martijn Pieters
Martijn Pieters

Reputation: 1124378

You can generate your list with a list comprehension easily:

if something in ['name_%d' % i for i in range(1, 6)]:

However, you are really just looking for a text pattern; a regular expression can test that for you more efficiently:

import re

if re.match(r'name_[1-6]$', something):

or you could just test if the string starts with a name:

if something.startswith('name_') and something[5:] in {'1', '2', '3', '4', '5'}:

In Python 3, using a static set literal to test for membership against is going to be faster than using a list. Both are cached in the bytecode as immutable structures, but testing against a set takes O(1) constant time, vs. O(N) time for a list. However, if you are generating the set or list, you still pay a O(N) cost just to build that object. Generating a list object is faster (no hashing required), which makes it ever so slightly faster to test against. A generator expression is slower still.

Timing comparisons for the comprehension approaches (generator expression, set and list comprehensions, for first, last and miss cases):

>>> import timeit
>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 6)): pass',
...               'something="name1"')
2.0784148779930547
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 6)}: pass',
...               'something="name1"')
2.032067227992229
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 6)]: pass',
...               'something="name1"')
1.9060910780681297
>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 6)): pass',
...               'something="name5"')
2.1426312710391358
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 6)}: pass',
...               'something="name5"')
2.0627736690221354
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 6)]: pass',
...               'something="name5"')
1.9719348540529609
>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 6)): pass',
...               'something="name42"')
2.160375243984163
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 6)}: pass',
...               'something="name42"')
2.0166494220029563
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 6)]: pass',
...               'something="name42"')
2.0706132350023836

For just 5 items, it's a bit of a wash, with perhaps the list comprehension winning.

For more 1000 possible names:

>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 1001)): pass',
...               'something="name1"', number=10**4)
3.895413015037775
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 1001)}: pass',
...               'something="name1"', number=10**4)
3.459794587106444
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 1001)]: pass',
...               'something="name1"', number=10**4)
3.510508105973713
>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 1001)): pass',
...               'something="name1000"', number=10**4)
3.792039962951094
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 1001)}: pass',
...               'something="name1000"', number=10**4)
3.859958241926506
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 1001)]: pass',
...               'something="name1000"', number=10**4)
3.561700245947577
>>> timeit.timeit('if something in ("name_{}".format(i) for i in range(1, 1001)): pass',
...               'something="name1009"', number=10**4)
3.616139759076759
>>> timeit.timeit('if something in {"name_{}".format(i) for i in range(1, 1001)}: pass',
...               'something="name1009"', number=10**4)
3.4787185511086136
>>> timeit.timeit('if something in ["name_{}".format(i) for i in range(1, 1001)]: pass',
...               'something="name1009"', number=10**4)
3.2148393219104037

The list comprehension still has the edge, by a small margin.

However, using a regular expression is easily faster than that, by a factor of 3:

>>> timeit.timeit('if re.match(r"name_[1-5]$", something): pass',
...               'import re; something="name1"')
0.7225337530253455
>>> timeit.timeit('if re.match(r"name_[1-5]$", something): pass',
...               'import re; something="name5"')
0.7184386339504272
>>> timeit.timeit('if re.match(r"name_[1-5]$", something): pass',
...               'import re; something="name42"')
0.7749457659665495

This can be made faster still by caching the re.compile(..) result.

The clear winner is simple text matching:

>>> timeit.timeit('if something.startswith("name_") and something[5:] in {"1", "2", "3", "4", "5"}: pass',
...               'something="name1"')
0.15361014590598643
>>> timeit.timeit('if something.startswith("name_") and something[5:] in {"1", "2", "3", "4", "5"}: pass',
...               'something="name5"')
0.14619109802879393
>>> timeit.timeit('if something.startswith("name_") and something[5:] in {"1", "2", "3", "4", "5"}: pass',
...               'something="name42"')
0.1544568829704076

This is about 15 times faster than the fastest list comprehension test.

Upvotes: 1

Meet Taraviya
Meet Taraviya

Reputation: 889

You can do:

if something in ['name_'+str(i+1) for i in range(5)]

Upvotes: -1

Adirio
Adirio

Reputation: 5286

Try the following:

if something in ["name_{}".format(i) for i in range(1, 6)]:

It uses list comprehension, the range(start, end) function (which includes the start but not the end) and str.format(), all quite pythonic.

Range by default starts at 0 so you could also go for:

if something in ["name_{}".format(i+1) for i in range(5)]:

I would also suggest swaping the list for a generator expresion:

if something in ("name_{}".format(i) for i in range(1, 6)):

if something in ("name_{}".format(i+1) for i in range(5)):

Upvotes: 2

Related Questions