str.startswith allows you to supply a tuple of strings to test for:
if link.lower().startswith(("js", "catalog", "script", "katalog")):
From the docs:
str.startswith(prefix[, start[, end]])Return
Trueif string starts with theprefix, otherwise returnFalse.prefixcan also be a tuple of prefixes to look for.
Below is a demonstration:
>>> "abcde".startswith(("xyz", "abc"))
True
>>> prefixes = ["xyz", "abc"]
>>> "abcde".startswith(tuple(prefixes)) # You must use a tuple though
True
>>>
Answer from user2555451 on Stack Overflowpython - str.startswith with a list of strings to test for - Stack Overflow
Python: startswith any alpha character - Stack Overflow
Strange behavior when using String.startswith() with an empty string and a big start
python - How to use str.startswith with multiple strings? - Stack Overflow
str.startswith allows you to supply a tuple of strings to test for:
if link.lower().startswith(("js", "catalog", "script", "katalog")):
From the docs:
str.startswith(prefix[, start[, end]])Return
Trueif string starts with theprefix, otherwise returnFalse.prefixcan also be a tuple of prefixes to look for.
Below is a demonstration:
>>> "abcde".startswith(("xyz", "abc"))
True
>>> prefixes = ["xyz", "abc"]
>>> "abcde".startswith(tuple(prefixes)) # You must use a tuple though
True
>>>
You can also use any(), map() like so:
if any(map(l.startswith, x)):
pass # Do something
Or alternatively, using a generator expression:
if any(l.startswith(s) for s in x)
pass # Do something
If you want to match non-ASCII letters as well, you can use str.isalpha:
if line and line[0].isalpha():
You can pass a tuple to startswiths() (in Python 2.5+) to match any of its elements:
import string
ALPHA = string.ascii_letters
if line.startswith(tuple(ALPHA)):
pass
Of course, for this simple case, a regex test or the in operator would be more readable.
I'd agree with the others that startswith is more readable, and you should use that. That said, if performance is a big issue for such a special case, benchmark it:
$ python -m timeit -s 'text="foo"' 'text.startswith("a")'
1000000 loops, best of 3: 0.537 usec per loop
$ python -m timeit -s 'text="foo"' 'text[0]=="a"'
1000000 loops, best of 3: 0.22 usec per loop
So text[0] is amost 2.5 times as fast - but it's a pretty quick operation; you'd save ~0.3 microseconds per compare depending on the system. Unless you're doing millions of comparisons in a time critical situation though, I'd still go with the more readable startswith.
text[0] fails if text is an empty string:
IronPython 2.6 Alpha (2.6.0.1) on .NET 4.0.20506.1
Type "help", "copyright", "credits" or "license" for more information.
>>> text = ""
>>> print(text.startswith("a"))
False
>>> print(text[0]=='a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: index out of range: 0
EDIT: You say you "know" that text is not empty... how confident are you of that, and what would you want to happen if it is empty in reality? If a failure is appropriate (e.g. it means a bug in your code) that would encourage the use of text[0]=='a'.
Other questions:
How concerned are you about the performance of this? If this is performance critical, then benchmark it on your particular Python runtime. I wouldn't be entirely surprised to find that (say) one form was faster on IronPython and a different one faster on CPython.
Which do you (and your team) find more readable?
aString = "hello world"
aString.startswith("hello")
More info about startswith.
RanRag has already answered it for your specific question.
However, more generally, what you are doing with
if [[ "$string" =~ ^hello ]]
is a regex match. To do the same in Python, you would do:
import re
if re.match(r'^hello', somestring):
# do stuff
Obviously, in this case, somestring.startswith('hello') is better.
There is technically no reason to accept other sequence types, no. The source code roughly does this:
if isinstance(prefix, tuple):
for substring in prefix:
if not isinstance(substring, str):
raise TypeError(...)
return tailmatch(...)
elif not isinstance(prefix, str):
raise TypeError(...)
return tailmatch(...)
(where tailmatch(...) does the actual matching work).
So yes, any iterable would do for that for loop. But, all the other string test APIs (as well as isinstance() and issubclass()) that take multiple values also only accept tuples, and this tells you as a user of the API that it is safe to assume that the value won't be mutated. You can't mutate a tuple but the method could in theory mutate the list.
Also note that you usually test for a fixed number of prefixes or suffixes or classes (in the case of isinstance() and issubclass()); the implementation is not suited for a large number of elements. A tuple implies that you have a limited number of elements, while lists can be arbitrarily large.
Next, if any iterable or sequence type would be acceptable, then that would include strings; a single string is also a sequence. Should then a single string argument be treated as separate characters, or as a single prefix?
So in other words, it's a limitation to self-document that the sequence won't be mutated, is consistent with other APIs, it carries an implication of a limited number of items to test against, and removes ambiguity as to how a single string argument should be treated.
Note that this was brought up before on the Python Ideas list; see this thread; Guido van Rossum's main argument there is that you either special case for single strings or for only accepting a tuple. He picked the latter and doesn't see a need to change this.
This has already been suggested on Python-ideas a couple of years back see: str.startswith taking any iterator instead of just tuple and GvR had this to say:
The current behavior is intentional, and the ambiguity of strings themselves being iterables is the main reason. Since
startswith()is almost always called with a literal or tuple of literals anyway, I see little need to extend the semantics.
In addition to that, there seemed to be no real motivation as to why to do this.
The current approach keeps things simple and fast,
unicode_startswith (and endswith) check for a tuple argument and then for a string one. They then call tailmatch in the appropriate direction. This is, arguably, very easy to understand in its current state, even for strangers to C code.
Adding other cases will only lead to more bloated and complex code for little benefit while also requiring similar changes to any other parts of the unicode object.
Noticed interesting thing: python startswith is 2 times slower then ==:
In [1]: k = "123123123"
In [2]: %timeit k[0] == "_"
50.1 ns ยฑ 1.83 ns per loop (mean ยฑ std. dev. of 7 runs, 10000000 loops each)
In [3]: %timeit k.startswith("_")
117 ns ยฑ 1.35 ns per loop (mean ยฑ std. dev. of 7 runs, 10000000 loops each)Tested with python 3.5, 3.6, 2.7
I think it is because CALL_FUNCTION instruction:
In [25]: dis.dis('k.startswith("_")')
1 0 LOAD_NAME 0 (k)
3 LOAD_ATTR 1 (startswith)
6 LOAD_CONST 0 ('_')
9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 RETURN_VALUEand == executes only on stack:
In [26]: dis.dis('k[0] == "_"')
1 0 LOAD_NAME 0 (k)
3 LOAD_CONST 0 (0)
6 BINARY_SUBSCR
7 LOAD_CONST 1 ('_')
10 COMPARE_OP 2 (==)
13 RETURN_VALUE(BINARY_SUBSCR is getitem instruction)
So be careful doing startswith, endswith and so on in huge cycles.
Also excellent article about python perfomance bottlenecks: https://gregoryszorc.com/blog/2019/01/10/what-i've-learned-about-optimizing-python/