The str.startswith(prefix[, start[, end]]) method returns True if the string begins with the specified prefix; otherwise, it returns False.
The Tuple-of-Prefixes Power Feature: The prefix argument accepts either a single string or a tuple of strings (e.g., ("http://", "https://")). When passed a tuple, Python returns True if the string matches any prefix in the collection. Note that passing a list raises a TypeError—prefixes must be encapsulated in a tuple.
The standard CPython signature for str.startswith() is:
str.startswith(prefix, start=0, end=sys.maxsize, /)
| Parameter | Type | Default | Description |
|---|---|---|---|
prefix |
str or tuple[str, ...] |
Required | The prefix string to search for, or a tuple of multiple candidate strings. |
start |
int |
0 |
Optional. 0-based beginning index (inclusive). Supports negative indices. |
end |
int |
End of string | Optional. 0-based termination index (exclusive). Supports negative indices. |
True or False). Testing an empty string prefix s.startswith("") always returns True.
Testing single substring prefixes at the start of a string:
banner = "Welcome to plus2net.com tutorials"
print("Starts with 'Welcome':", banner.startswith("Welcome"))
print("Starts with 'welcome':", banner.startswith("welcome")) # Case sensitive!
print("Starts with 'Python': ", banner.startswith("Python"))
Expected Output:
Starts with 'Welcome': True
Starts with 'welcome': False
Starts with 'Python': False
Matching is strictly case-sensitive. For case-insensitive checking, normalize the prefix and target: text.lower().startswith("welcome").
One of Python's most convenient features is passing a tuple of prefixes. Python returns True if the string starts with any candidate in the tuple:
valid_schemes = ("https://", "http://", "ftp://", "sftp://")
url1 = "https://api.plus2net.com/v1"
url2 = "ftp://files.example.org"
url3 = "mailto:support@plus2net.com"
print("URL 1 valid:", url1.startswith(valid_schemes))
print("URL 2 valid:", url2.startswith(valid_schemes))
print("URL 3 valid:", url3.startswith(valid_schemes))
Expected Output:
URL 1 valid: True
URL 2 valid: True
URL 3 valid: False
["https://", "http://"]) raises:
TypeError: startswith first arg must be str or a tuple of str, not list.
tuple(my_list) before passing to startswith().
The start and end parameters restrict the evaluation to a specific index range without allocating a new slice string in memory:
text = "Data Science and Machine Learning"
# Check if substring starting at index 5 begins with 'Science'
print("At offset 5: ", text.startswith("Science", 5))
# Check range [5:12]
print("Range [5:12]: ", text.startswith("Science", 5, 12))
# Negative start offset: last 8 characters
print("Last 8 chars: ", text.startswith("Learning", -8))
Expected Output:
At offset 5: True
Range [5:12]: True
Last 8 chars: True
Developers sometimes write s[:5] == "https" instead of s.startswith("https"). While both yield boolean results, startswith() offers critical advantages:
| Feature | str.startswith("prefix") |
Manual Slice s[:len] == "prefix" |
|---|---|---|
| Memory Allocation | Zero (inspects bytes directly in C) | Allocates temporary slice string |
| Multiple Prefixes | Native tuple support | Requires multiple `or` comparisons |
| Readability & Intent | Explicit and self-documenting | Magical length slicing numbers |
| Safety on Short Strings | Safe (returns False cleanly) | Safe (slicing does not raise IndexError) |
Preventing directory traversal attacks (../) by verifying that canonical paths start within an authorized root:
import os
def is_safe_path(target_path: str, base_dir: str = "/var/www/uploads") -> bool:
# Resolve real absolute paths
real_target = os.path.realpath(target_path)
real_base = os.path.realpath(base_dir)
return real_target.startswith(real_base + os.sep)
print("Safe path: ", is_safe_path("/var/www/uploads/avatar.png"))
print("Escape path: ", is_safe_path("/var/www/uploads/../../etc/passwd"))
Expected Output:
Safe path: True
Escape path: False
Filtering configuration and script lines supporting multiple comment delimiters:
def is_comment_line(line: str) -> bool:
cleaned = line.strip()
comment_markers = ("#", "//", ";", "--", "/*", "REM")
return cleaned.startswith(comment_markers)
lines = [
"# Python / Bash comment",
"// JavaScript comment",
"-- SQL comment",
"SELECT * FROM users;",
"PORT = 8080"
]
code_lines = [ln for ln in lines if not is_comment_line(ln)]
print("Filtered executable lines:\n", "\n ".join(code_lines))
Expected Output:
Filtered executable lines:
SELECT * FROM users;
PORT = 8080
Categorizing uploaded media files by standard prefix conventions:
def classify_asset(filename: str) -> str:
name_upper = filename.upper()
if name_upper.startswith(("IMG_", "PHOTO_", "DCIM_")):
return "IMAGE"
elif name_upper.startswith(("VID_", "MOV_", "CLIP_")):
return "VIDEO"
elif name_upper.startswith(("DOC_", "REPORT_", "INVOICE_")):
return "DOCUMENT"
return "UNKNOWN"
print("IMG_2026.png: ", classify_asset("IMG_2026.png"))
print("vid_clip_01.mp4: ", classify_asset("vid_clip_01.mp4"))
print("invoice_9410.pdf: ", classify_asset("invoice_9410.pdf"))
Expected Output:
IMG_2026.png: IMAGE
vid_clip_01.mp4: VIDEO
invoice_9410.pdf: DOCUMENT
Prefix candidates must be packaged in a tuple, not a list:
prefixes = ["http://", "https://"]
# TypeError: startswith first arg must be str or a tuple of str, not list
"https://plus2net.com".startswith(prefixes)
# Correct: convert to tuple
"https://plus2net.com".startswith(tuple(prefixes))
s.startswith("") returns True for any string s (including an empty string ""). If dynamically checking user-supplied search filters, ensure the query is validated to be non-empty.
Below is a production-grade utility class, PrefixMatcher, demonstrating protocol routing, comment filtering, and automated unit assertions:
class PrefixMatcher:
@staticmethod
def has_valid_protocol(url: str) -> bool:
"""Validates web or transport protocol prefixes."""
valid_schemes = ("https://", "http://", "ftp://", "sftp://")
return url.lower().startswith(valid_schemes)
@staticmethod
def is_code_comment(line: str) -> bool:
"""Tests if a line starts with common comment characters."""
markers = ("#", "//", "--", ";")
return line.strip().startswith(markers)
@staticmethod
def match_at_index(text: str, prefix: str, offset: int) -> bool:
"""Checks prefix at designated offset."""
return text.startswith(prefix, offset)
# ==========================================
# SELF-CHECKING UNIT ASSERTIONS
# ==========================================
matcher = PrefixMatcher()
# 1. Single string matching
assert "Python".startswith("Py") is True
assert "Python".startswith("py") is False # Case sensitive
# 2. Tuple matching
assert matcher.has_valid_protocol("https://plus2net.com") is True
assert matcher.has_valid_protocol("sftp://files.host.net") is True
assert matcher.has_valid_protocol("gopher://old.net") is False
# 3. Comment line filtering
assert matcher.is_code_comment(" # This is a comment") is True
assert matcher.is_code_comment(" -- SQL comment") is True
assert matcher.is_code_comment("var x = 10;") is False
# 4. Offset matching
assert matcher.match_at_index("ABCDEF", "CD", 2) is True
assert matcher.match_at_index("ABCDEF", "CD", 3) is False
# 5. Empty string prefix is always True
assert "anything".startswith("") is True
assert "".startswith("") is True
# 6. TypeError validation when passing a list
try:
"https://example.com".startswith(["https://", "http://"])
assert False, "Expected TypeError not raised"
except TypeError as e:
assert "must be str or a tuple" in str(e)
print("Protocol valid check: ", matcher.has_valid_protocol("https://api.plus2net.com"))
print("Comment line check: ", matcher.is_code_comment("// Config line"))
print("Offset check: ", matcher.match_at_index("12345", "34", 2))
print("All str.startswith() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all prefix detection examples interactively, test tuple-based multiple prefix searches, benchmark startswith() against slicing, and guard paths in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.