I keep running into the same data-cleaning pain point: you have a string that looks human-readable, but the punctuation is noisy for your downstream task. Think log lines, customer feedback, CSV dumps, or scraped snippets. If you’re doing token-based analytics, building a keyword index, or feeding text to a quick classifier, punctuation becomes friction. Remove too much and you lose meaning. Remove too little and you keep noise. I’ve done this in ETL jobs, chat pipelines, and in small scripts where I just need a clean signal fast.
What I’ll do here is walk you through multiple approaches to removing punctuation in Python, starting with the fastest, then moving into the flexible ones. I’ll show complete, runnable examples and explain when each method shines, along with the edge cases I see most often. You’ll also get practical guidance on when not to strip punctuation, because some punctuation is part of the data, not just decoration. My goal is that by the end you can pick the right method in seconds and defend that choice in a code review.
Fast path: translate + maketrans for ASCII punctuation
If you want raw speed and a simple rule—remove common punctuation defined by Python’s standard library—I recommend str.translate() with a translation table from str.maketrans(). In my experience this is the fastest method for large volumes of text because it pushes the work into efficient C loops and avoids Python-level branching per character.
Here’s a clean, complete example you can run as-is:
import string
s = "Hello, World! Python is amazing."
# Translation table that deletes punctuation characters
translator = str.maketrans(‘‘, ‘‘, string.punctuation)
clean_text = s.translate(translator)
print(clean_text)
Output:
Hello World Python is amazing
Why I use this:
string.punctuationgives you a standard ASCII set:!"#$%&‘()*+,-./:;?@[\]^_{|}~`.str.maketrans(‘‘, ‘‘, ...)builds a deletion map, not a replacement map, so it’s compact.translate()applies the map in one pass, which is usually faster than Python loops.
When you should choose this:
- You’re cleaning large logs or datasets where punctuation isn’t meaningful.
- You want a stable, predictable set of removed characters.
- You care about speed more than custom rules.
When I avoid it:
- You need to keep apostrophes for contractions.
- Your input contains non-ASCII punctuation (smart quotes, em dashes, “¿”, etc.).
- You’re cleaning structured data like decimals in numbers or URLs, where punctuation carries meaning.
A small but important nuance: string.punctuation is ASCII-only. If your data includes “—” or “…” or “« »”, they won’t be removed unless you expand the removal set.
Regex when rules are more than punctuation
Regular expressions are slower than translate(), but they’re flexible. I reach for re.sub() when I need to keep some punctuation and discard others, or when punctuation is just one piece of a bigger filter rule.
Example: remove anything that is not a word character or whitespace.
import re
s = "Text cleaning? Regex-based! Works fine."
clean = re.sub(r"[^\w\s]", "", s)
print(clean)
Output:
Text cleaning Regexbased Works fine
What this pattern means:
\wmatches letters, digits, and underscore.\smatches whitespace.[^\w\s]matches anything that is not a word character and not whitespace.
This is a broad brush. It will remove hyphens, punctuation marks, and symbols. It will also remove emoji and many non-Latin symbols. If that’s what you want, great. If not, you’ll need a more specific pattern.
A more targeted rule: keep apostrophes inside words (for English contractions) while removing other punctuation.
import re
s = "I‘m happy—aren‘t you? It‘s 2026, after all."
# Remove punctuation except apostrophes between letters
clean = re.sub(r"(?!\b\w+‘\w+\b)[^\w\s‘]", "", s)
print(clean)
Output:
I‘m happy arent you It‘s 2026 after all
Note the tradeoff: we kept apostrophes inside words, but removed em dashes and question marks. We also kept the apostrophe in "I‘m" and "It‘s". We did lose the apostrophe in "aren‘t" because I filtered it differently; the pattern can be tuned further if needed.
When I pick regex:
- You need conditional rules (keep some punctuation, remove others).
- You want to preserve structure like hashtags or email addresses.
- You need to remove multiple classes of characters, not just punctuation.
When I avoid regex:
- You’re processing very large text blobs at scale and speed matters more than nuance.
- Your rule can be expressed as a simple deletion set (use
translate()instead).
Character filtering with list comprehension
List comprehension is a great middle ground when you want clarity and control without regex. It’s readable, testable, and you can drop in simple conditions for alphanumeric and whitespace. It won’t be as fast as translate(), but it’s easy to reason about and good for medium-sized strings.
Here’s a straightforward version using string.punctuation:
import string
s = "Hello, World! Python is amazing."
clean = ‘‘.join([ch for ch in s if ch not in string.punctuation])
print(clean)
Output:
Hello World Python is amazing
If you want to keep only letters, digits, and spaces, you can avoid the punctuation list and filter by character properties:
s = "Trade-offs: 3.5% growth in Q4."
clean = ‘‘.join([ch for ch in s if ch.isalnum() or ch.isspace()])
print(clean)
Output:
Tradeoffs 35 growth in Q4
This shows a key risk: removing punctuation can change meaning. The decimal 3.5 became 35, and the hyphen in Trade-offs disappeared. If those matter to you, you should keep those characters explicitly.
I sometimes use a whitelist approach like this:
allowed = set("‘-.%") # keep apostrophes, hyphens, dots, percent
clean = ‘‘.join([ch for ch in s if ch.isalnum() or ch.isspace() or ch in allowed])
This makes your intent very clear and is easy to modify.
When list comprehension shines:
- You want clarity and easy unit tests.
- You’re building a small script or a data-cleaning step in an ETL job.
- You need simple whitelists or blacklists that are easy to tweak.
When I don’t pick it:
- You’re processing massive datasets and need maximum throughput.
- You want to support full Unicode punctuation sets (see the Unicode section below).
Functional style with filter()
The filter() approach is similar to list comprehension but can read nicely when combined with a named predicate. I use it when I want the filtering rule to be re-usable or when I want to write tests around the predicate itself.
Here’s a clean example:
import string
s = "Filtering… Is it clean now?"
def keep_char(ch: str) -> bool:
return ch not in string.punctuation
clean = ‘‘.join(filter(keep_char, s))
print(clean)
Output:
Filtering Is it clean now
You can also inline the predicate with a lambda, but I prefer a named function for clarity, type hints, and tests.
If you want to keep a custom set of punctuation, the predicate makes that explicit:
import string
s = "Email me: [email protected]!"
allowed = set(".@")
def keep_char(ch: str) -> bool:
return ch.isalnum() or ch.isspace() or ch in allowed
clean = ‘‘.join(filter(keep_char, s))
print(clean)
Output:
Email me [email protected]
That’s simple and clear. For most business pipelines this is more than enough.
Unicode punctuation and international text
This is where most “simple” approaches fail. If your text includes curly quotes, em dashes, ellipses, or punctuation from other languages, string.punctuation does not cover them. This matters in 2026 because data is increasingly global, and even English text often includes smart quotes from word processors.
To remove punctuation in a Unicode-aware way, you can check each character’s Unicode category. Punctuation categories begin with P (like Pd for dash punctuation, Ps for open punctuation, etc.). Here’s a Unicode-safe approach:
import unicodedata
s = "“Wait — what?” she asked…"
def is_punctuation(ch: str) -> bool:
return unicodedata.category(ch).startswith(‘P‘)
clean = ‘‘.join(ch for ch in s if not is_punctuation(ch))
print(clean)
Output:
Wait what she asked
That removes curly quotes and the ellipsis, which string.punctuation would miss. If you want to keep some of those marks (say, hyphens), adjust the predicate. For example, you might allow Unicode dash punctuation:
def is_punctuation(ch: str) -> bool:
cat = unicodedata.category(ch)
if cat == ‘Pd‘:
return False # keep dashes
return cat.startswith(‘P‘)
This gives you fine-grained control across languages.
If you’re already using regular expressions and want full Unicode support, consider the third-party regex module (not the built-in re). It supports properties like \p{P} to match punctuation classes. I only use this when I’m already depending on regex, because it’s an extra dependency for many projects.
Key pitfalls I see here:
- Unicode punctuation removed, but you forgot to normalize whitespace; you end up with double spaces.
- You removed punctuation that was part of tokens (like “C#” or “U.S.”) and lost semantics.
- You removed emoji, which may actually be meaningful features in social or sentiment data.
If you’re building a user-facing system, test with realistic multilingual samples, not just ASCII.
Meaning matters: when to keep punctuation
I often say this to teams: punctuation is not always noise. In product data, punctuation can be part of a model number. In finance, commas and periods in numbers are significant. In support tickets, exclamation points might correlate with urgency. If you delete punctuation blindly, your metrics or features can drift.
Here are a few practical examples:
1) Contractions and possessives
- You probably want to keep apostrophes in English (“don’t”, “Jane’s”).
- If you remove them, you alter word tokens and might degrade search results.
2) Numbers and decimals
3.5becoming35changes the meaning.- If you need numbers, whitelist
.and,and then normalize.
3) URLs and emails
- Periods and slashes are part of identifiers. Removing them destroys the value.
- You might want to keep
.@:/-for these tokens, or strip punctuation only after extracting URLs/emails separately.
4) Hyphenated terms
- “real-time” vs “realtime” might be acceptable in some contexts.
- For search, you may want to split on hyphens, not remove them entirely.
A practical pattern I use: extract structured items first (emails, URLs, product codes), then remove punctuation from the remaining free text. That prevents “over-cleaning.”
Performance and practical testing in 2026 workflows
I see teams either overthink performance or ignore it. Here’s a sane way to reason about it. For small strings, any method is fine. For large datasets, translate() is usually the fastest, list comprehension is next, filter() is similar, and regex is slower. The difference is often visible at scale, not on a single line of text. For example, on a few megabytes of text, translate() might run in the 5–15ms range, while regex can be 20–60ms. The numbers vary by hardware and Python version, but the relative ordering holds in practice.
If you want to compare approaches, use a short benchmark with realistic text. Here’s a simple pattern you can adapt (I often drop this into a quick script):
import time
import string
s = ("Hello, World! Python is amazing. " * 10000)
translator = str.maketrans(‘‘, ‘‘, string.punctuation)
def run(name, fn):
start = time.perf_counter()
fn()
end = time.perf_counter()
print(f"{name}: {end – start:.4f}s")
run("translate", lambda: s.translate(translator))
Note: this is a micro-benchmark. It’s fine for comparing relative speed, but don’t treat it as a production performance test.
In modern pipelines, I pair these approaches with lightweight checks:
- Add unit tests for edge cases: decimals, contractions, URLs.
- Use type hints on helper functions so the behavior is clear in code reviews.
- Run
rufforpyrightto catch tiny mistakes in helper logic. - If you’re using
uvfor dependency management or CI caching, lock dependencies so your regex behavior stays stable.
When choosing a method, I make the decision like this:
- If speed matters and rules are simple, I use
translate(). - If meaning matters and rules are nuanced, I choose list comprehension or regex.
- If Unicode punctuation matters, I use Unicode category filtering.
Traditional vs modern approach table
I use a quick comparison like this with teams, especially when a project mixes legacy scripts with newer tooling.
Modern approach (2026)
—
re.sub() with a broad pattern translate() with a deletion table
List comprehension with clear whitelist/blacklist
Unicode category filtering via unicodedata
Named predicate with type hints
The “modern” choice doesn’t mean you must add dependencies or rewrite everything. It just means you pick the method that makes intent and behavior explicit, and you test the edge cases that affect your data’s meaning.
Common mistakes I see (and how to avoid them)
I’ve reviewed enough text-cleaning code to notice recurring issues. Here’s what I look for in reviews, and what you should test early:
- Removing punctuation from numeric values without noticing:
3.5to35is a silent data error. If numbers matter, whitelist.and,, then normalize. - Treating underscores as punctuation:
\win regex includes_, sore.sub(r"[^\w\s]", "", s)keeps underscores. If you want them removed, add an explicit rule. - Stripping apostrophes and breaking contractions: if you care about language features, keep
’and‘and normalize curly quotes to a straight apostrophe first. - Ignoring Unicode punctuation: smart quotes and em dashes remain in the text unless you handle them.
- Forgetting whitespace cleanup: punctuation removal can leave double spaces. A final pass like
‘ ‘.join(clean.split())can tidy output, but it also collapses line breaks, so use it carefully.
I recommend writing a small set of representative examples and test them in your pipeline. A few lines in a unit test are enough to save hours of debugging later.
Practical patterns I use in real projects
Here are two small patterns I’ve used repeatedly that you can copy as-is.
1) Keep contractions and decimals, remove other punctuation
import string
s = "It‘s 3.5% growth in Q4—impressive!"
allowed = set("‘.%") # keep apostrophes, dots, percent
remove = set(string.punctuation) – allowed
translator = str.maketrans(‘‘, ‘‘, ‘‘.join(remove))
clean = s.translate(translator)
print(clean)
Output:
It‘s 3.5% growth in Q4—impressive
Note: the em dash is still there. If you want it removed too, add a Unicode pass or normalize it first.
2) Extract URLs, then clean the rest
import re
import string
text = "Visit https://example.com/docs/v2.1?ref=home! It’s great."
urls = re.findall(r"https?://\S+", text)
# Remove URLs before punctuation cleaning
without_urls = re.sub(r"https?://\S+", "", text)
translator = str.maketrans(‘‘, ‘‘, string.punctuation)
clean = without_urls.translate(translator)
print("URLs:", urls)
print("Text:", clean)
Output:
URLs: [‘https://example.com/docs/v2.1?ref=home!‘]
Text: Visit It’s great
This pattern is helpful when punctuation is meaningful inside URLs but not in general text.
Deeper dive: what counts as punctuation in Python
It sounds simple, but “punctuation” is ambiguous. Python’s string.punctuation is a set of 32 ASCII characters. It does not include unicode punctuation or symbols like currency signs.
If your job is strictly ASCII cleaning, string.punctuation is enough. If you’re dealing with content from word processors, social data, or multilingual sources, then unicode category filtering is the safer default.
Here’s a diagnostic helper I use to check what a string actually contains:
import unicodedata
s = "“Hi”—said the developer…"
for ch in s:
if not ch.isalnum() and not ch.isspace():
print(repr(ch), unicodedata.name(ch), unicodedata.category(ch))
Typical output:
‘“‘ LEFT DOUBLE QUOTATION MARK Pi
‘”‘ RIGHT DOUBLE QUOTATION MARK Pf
‘—‘ EM DASH Pd
‘…‘ HORIZONTAL ELLIPSIS Po
This tells you exactly which categories appear in your text and helps you craft precise rules.
Cleaning pipeline design: a practical checklist
I’ve seen cleaning logic grow slowly until it becomes brittle. To keep it maintainable, I follow a lightweight checklist:
- Define your goal: is this for search indexing, feature engineering, or display? The answer changes the punctuation rules.
- Choose a baseline method:
translate()for speed,unicodedatafor breadth, regex for conditional logic. - Decide what must be preserved: decimals, URLs, hashtags, or model numbers.
- Apply normalization only where needed: avoid collapsing all whitespace if line breaks or sentence boundaries matter.
- Add tests for each exception case: a handful of curated inputs goes a long way.
When I document the cleaning step, I explicitly list what punctuation is kept and why. That single paragraph prevents a lot of future confusion.
Edge cases you should consciously handle
I’ll list the edge cases that show up most often and how I approach them.
1) Curly quotes and apostrophes
If your data comes from rich text, you’ll likely see “” or ’. I normalize them before removing punctuation:
replacements = {
‘“‘: ‘"‘,
‘”‘: ‘"‘,
‘’‘: "‘",
‘‘‘: "‘",
}
s = "“Don’t” break contracts.”"
for k, v in replacements.items():
s = s.replace(k, v)
2) Ellipses and em dashes
These are punctuation but also signal pauses or emphasis. I remove them only if the downstream task doesn’t use sentiment or style features. Otherwise, I normalize them to a space or a simpler token like ....
3) Apostrophes at word boundaries
Sometimes you want to keep apostrophes inside words but remove them elsewhere (like in quotes). A simple heuristic is to keep apostrophes between letters and remove them otherwise. This is easiest with regex, but you can also do it with a loop.
4) File paths and identifiers
Paths like C:\Users\Sam or src/app.py look like punctuation but are meaningful. If file paths matter, extract them before cleaning or whitelist / and ..
5) Underscores and hyphens in IDs
Some identifiers use underscores or hyphens as separators. If you remove them, IDs might merge and become ambiguous. This is a common source of silent errors in log analytics.
The takeaway: don’t assume punctuation is always noise. Identify the real tokens you care about and design the cleaning step around them.
A reusable, configurable helper function
When a team needs a consistent cleaning step, I often wrap it in a helper that makes intent clear and avoids surprise behavior. Here’s a version that is flexible but still compact.
import string
import unicodedata
from typing import Iterable
def remove_punctuation(
text: str,
*,
keep: Iterable[str] = (),
unicode_aware: bool = False,
) -> str:
keep_set = set(keep)
if unicode_aware:
def is_punct(ch: str) -> bool:
return unicodedata.category(ch).startswith(‘P‘) and ch not in keep_set
return ‘‘.join(ch for ch in text if not is_punct(ch))
else:
removal = set(string.punctuation) – keep_set
translator = str.maketrans(‘‘, ‘‘, ‘‘.join(removal))
return text.translate(translator)
Example usage:
s = "It‘s 3.5%—nice!"
print(removepunctuation(s, keep="‘.%", unicodeaware=True))
Output:
It‘s 3.5%nice
Notice the em dash is removed. If you want a space there, add a normalization step before cleaning, such as replacing em dash with space. That’s a good example of a small design choice that can materially change results.
When not to remove punctuation at all
Sometimes the right answer is to keep punctuation and tokenize differently. Here are cases where I avoid removal entirely:
- Sentence segmentation or readability scoring: punctuation is signal, not noise.
- Code or config parsing: punctuation defines structure; removing it breaks meaning.
- Customer support analytics where tone matters: exclamation marks and question marks can matter.
- Named entity extraction: punctuation in names (e.g., “AT&T”, “O’Connor”) can be part of identity.
In those cases, I normalize punctuation rather than remove it. For example, I might replace curly quotes with straight quotes, or multiple punctuation marks with a single one.
Whitespace normalization after punctuation removal
One subtle issue: deleting punctuation can create odd spacing, especially if punctuation was adjacent to spaces. Here’s a safe cleanup approach I use when I know I want a single-space result:
def normalize_spaces(s: str) -> str:
return ‘ ‘.join(s.split())
This collapses all runs of whitespace into a single space and trims ends. It also removes line breaks, which may be undesirable if you want to preserve lines. For multi-line text, I sometimes normalize per line instead:
def normalizespacesper_line(s: str) -> str:
return ‘\n‘.join(‘ ‘.join(line.split()) for line in s.splitlines())
Whether you do this depends on the downstream use case. For token-based analytics, collapsing spaces is usually fine. For display or logs, it might be a problem.
Real-world scenarios and how I choose the method
I’ll make this concrete with a few scenarios I’ve seen.
1) Customer feedback sentiment
- Goal: extract meaningful words, keep expressive punctuation if it signals sentiment.
- I use Unicode-aware filtering but keep
!and?and normalize repeated punctuation. - Rationale: “great!!!” might be a useful signal.
2) Product catalog normalization
- Goal: normalize names, keep model numbers and hyphens.
- I use list comprehension with a whitelist for
-and.and preserve case. - Rationale: “XJ-900” shouldn’t become “XJ900” if downstream systems expect the hyphen.
3) Log analytics for keywords
- Goal: strip punctuation, get clean tokens fast.
- I use
translate()with ASCII punctuation and then lowercase. - Rationale: high volume, limited meaning from punctuation.
4) Multilingual chat data
- Goal: clean text for a classifier that supports multiple languages.
- I use Unicode category filtering and avoid removing non-Latin letters.
- Rationale:
string.punctuationis insufficient and will miss many marks.
These choices aren’t “right” or “wrong.” The important part is you can explain the tradeoffs and the reason for the rule set.
Mini performance comparison you can run locally
If you want a quick feel for speed in your environment, I use this short benchmark. It’s not about absolute numbers; it’s about relative behavior:
import string
import re
import time
import unicodedata
s = ("Hello—World! Python, in 2026… is great. " * 10000)
translator = str.maketrans(‘‘, ‘‘, string.punctuation)
def translate_ascii():
return s.translate(translator)
def regex_basic():
return re.sub(r"[^\w\s]", "", s)
def unicode_filter():
return ‘‘.join(ch for ch in s if not unicodedata.category(ch).startswith(‘P‘))
def bench(fn, name):
start = time.perf_counter()
fn()
end = time.perf_counter()
print(name, end – start)
bench(translate_ascii, "translate")
bench(regex_basic, "regex")
bench(unicode_filter, "unicode")
You’ll likely see translate() at the top, regex slower, and Unicode filtering somewhere between, depending on the dataset and Python version. Use this to guide decisions, not to benchmark production performance.
Guidance on choosing a punctuation rule set
I’ll wrap the decision in a simple decision path that I personally use:
- Start with the data: is it ASCII-only or multilingual? If multilingual, use Unicode filtering or
regexwith Unicode properties. - Decide what you must preserve: decimals, apostrophes, URLs, hashtags, model IDs.
- Pick the implementation based on performance vs clarity.
- Add 5–10 test cases that encode your edge cases.
If you do just those four steps, you avoid most of the mistakes I see in reviews.
Expanded “keep” strategies you can adapt
Sometimes it’s easier to define what to keep rather than what to remove. Here are a few patterns:
Keep only letters and spaces (good for aggressive keyword extraction):
def keeplettersspaces(s: str) -> str:
return ‘‘.join(ch for ch in s if ch.isalpha() or ch.isspace())
Keep letters, digits, and a curated punctuation set:
def keepalnumand(s: str, extra: str) -> str:
allowed = set(extra)
return ‘‘.join(ch for ch in s if ch.isalnum() or ch.isspace() or ch in allowed)
Keep hashtags and mentions for social data:
def keepsocialtokens(s: str) -> str:
allowed = set("#@_")
return ‘‘.join(ch for ch in s if ch.isalnum() or ch.isspace() or ch in allowed)
These patterns are readable, testable, and easy to evolve.
Keeping structure: punctuation as separators, not noise
Sometimes you don’t want to remove punctuation, you want to treat it as a boundary. For example, you might want to split tokens but not delete punctuation inside tokens.
One strategy is to replace punctuation with spaces rather than delete it. This avoids accidental token merging:
import string
s = "real-time, high-quality."
translator = str.maketrans({ch: ‘ ‘ for ch in string.punctuation})
clean = s.translate(translator)
clean = ‘ ‘.join(clean.split())
print(clean)
Output:
real time high quality
This approach often works well for search indexing or bag-of-words models because you avoid merging words.
A note on emojis and symbols
Emojis are not punctuation, but they’re often removed by broad regex rules. That’s fine if you don’t want them, but it can be a mistake for sentiment tasks. If emoji matter, do not use broad patterns like [^\w\s] unless you’re sure you want to drop them.
If you want to keep emoji but drop punctuation, Unicode category filtering is the safer way, because emojis are category So (symbol, other), not P. They won’t be removed by a startswith(‘P‘) test.
Tests I actually write
Here are the tests I usually add when shipping a cleaning helper. These are minimal but high-impact:
def test_contractions():
s = "I can‘t do that."
assert remove_punctuation(s, keep="‘") == "I can‘t do that"
def test_decimals():
s = "Growth is 3.5%."
assert remove_punctuation(s, keep=".%") == "Growth is 3.5%"
def testunicodepunct():
s = "“Hello”—world…"
assert removepunctuation(s, unicodeaware=True) == "Hello world"
def testurlsextracted_first():
s = "Visit https://example.com/test.html now!"
# Here I would extract URLs first, then clean the rest.
assert True
The exact assertions depend on your helper, but the point is to test the semantics you care about, not just the mechanics.
Practical advice on explainability
If your cleaning logic is non-trivial, add a comment or docstring that says why each punctuation is kept or removed. This helps in code reviews and reduces accidental “cleanup” changes later. I’ve found this to be especially important in teams where multiple services share the same cleaning function.
Here’s a simple docstring format I use:
def clean_text(s: str) -> str:
"""
Removes punctuation for keyword analysis.
Preserves: apostrophes (contractions), dots/percent (decimals).
Unicode-aware: yes, drops dashes and quotes.
"""
…
Clear and short, but it captures intent.
Summary: picking the right method quickly
If you want a simple heuristic to memorize, this is the one I use:
- Use
translate()when your rule is “remove ASCII punctuation fast.” - Use list comprehension or
filter()when you want clarity and easy customization. - Use Unicode category filtering when you’re dealing with rich or multilingual text.
- Use regex when the rule is conditional or spans multiple character classes.
The “best” approach is the one that preserves meaning for your task and is easy for your team to maintain. The good news is you can always start with a simple method and evolve it as you see the edge cases.
Quick reference: one-screen decision table
Recommended method
—
translate()
list comprehension
unicodedata category
regex
translate to spaces
If you take nothing else away: be deliberate about punctuation. It’s not always noise, and your cleaning step should reflect the meaning you care about, not just what’s easiest to code.


