String manipulation
character search
programming tips
coding
Python

How to check a string for specific characters?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Checking a string for specific characters is a common validation task in forms, file names, and parsing pipelines. The tricky part is usually not syntax, but defining exactly what is allowed and how to handle Unicode, whitespace, and empty input. This guide uses practical Python examples to build checks that are clear, fast, and easy to test.

Start by Defining the Validation Rule

Before writing code, define the rule in plain language. Examples:

  • Allow only letters, digits, underscore, and hyphen.
  • Require at least one digit.
  • Reject whitespace anywhere in the value.

When rules are explicit, you can choose the simplest implementation and avoid hidden assumptions. Validation bugs usually come from unclear rules, not complex code.

Method 1: Character Set Check with all

For simple allowlists, iterating through characters is readable and efficient.

python
1import string
2
3ALLOWED = set(string.ascii_letters + string.digits + "_-")
4
5def contains_only_allowed(text: str) -> bool:
6    if not text:
7        return False
8    return all(ch in ALLOWED for ch in text)
9
10samples = ["good_name-01", "bad name", "", "ok_2"]
11for s in samples:
12    print(s, contains_only_allowed(s))

Why this works well:

  • Rule is visible in one place.
  • Easy to add or remove characters.
  • Fast enough for most request validation workloads.

If you need to report which character failed, switch to a loop and return a detailed message.

Method 2: Pattern Check with re.fullmatch

Regular expressions are useful when the rule includes ranges, repetition, or optional segments.

python
1import re
2
3PATTERN = re.compile(r"[A-Za-z0-9_-]+")
4
5def is_valid_identifier(text: str) -> bool:
6    return bool(PATTERN.fullmatch(text))
7
8tests = ["alpha", "alpha-2", "has space", "ends."]
9for value in tests:
10    print(value, is_valid_identifier(value))

Use fullmatch, not search, when validating an entire string. search can return true even when only part of the input matches.

Method 3: Require Specific Character Types

Sometimes the rule is not only allowlist but also minimum composition, such as at least one digit and one letter.

python
1def has_letter_and_digit(text: str) -> bool:
2    if not text:
3        return False
4    has_letter = any(ch.isalpha() for ch in text)
5    has_digit = any(ch.isdigit() for ch in text)
6    return has_letter and has_digit
7
8for sample in ["abc", "123", "a1", "A-9"]:
9    print(sample, has_letter_and_digit(sample))

This style is often easier to maintain than a dense regex when requirements are likely to change.

Handle Unicode and Normalization Deliberately

If your application accepts international input, normalize text before validation. Equivalent characters can have different binary representations.

python
1import unicodedata
2
3def normalize_text(text: str) -> str:
4    return unicodedata.normalize("NFC", text)
5
6raw = "Cafe\u0301"  # e plus combining accent
7normalized = normalize_text(raw)
8print(raw == normalized)         # False
9print(len(raw), len(normalized))

For security sensitive contexts, consider stricter normalization and explicit script restrictions. User friendly display rules and security rules are not always the same.

Build a Reusable Validator with Error Messages

A small reusable function improves API responses and debugging.

python
1from dataclasses import dataclass
2
3@dataclass
4class ValidationResult:
5    ok: bool
6    message: str
7
8ALLOWED_CHARS = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
9
10def validate_username(text: str) -> ValidationResult:
11    if not text:
12        return ValidationResult(False, "username is required")
13    if len(text) < 3 or len(text) > 20:
14        return ValidationResult(False, "username length must be between 3 and 20")
15    bad = [ch for ch in text if ch not in ALLOWED_CHARS]
16    if bad:
17        return ValidationResult(False, "invalid characters: " + "".join(sorted(set(bad))))
18    return ValidationResult(True, "ok")
19
20print(validate_username("ok_name-1"))
21print(validate_username("no space"))

This approach keeps validation logic centralized and avoids duplicate ad hoc checks across endpoints.

Common Pitfalls

  • Using re.search when you intended to validate the whole string.
  • Forgetting empty input handling, which can accidentally pass in some checks.
  • Mixing business rules and parser rules in one function, making changes risky.
  • Ignoring Unicode normalization when accepting non ASCII input.
  • Returning only true or false without an error reason, which slows debugging.

Summary

  • Define the rule clearly before picking a technique.
  • Use all with an allowlist for simple readable validation.
  • Use re.fullmatch when pattern rules are naturally expressed with regex.
  • Add composition checks with any for requirements like letter plus digit.
  • Normalize Unicode deliberately and return clear validation messages for maintainability.

Course illustration
Course illustration

All Rights Reserved.