Reputation: 385
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
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
Reputation: 889
You can do:
if something in ['name_'+str(i+1) for i in range(5)]
Upvotes: -1
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