← Назад к вопросам

Валидация логина

1.0 Junior🔥 171 комментариев
#Python Core#Безопасность

Условие

Напишите функцию валидации логина по правилам:

  • Должен начинаться с латинской буквы
  • Может содержать латинские буквы, цифры, точки и дефисы
  • Должен заканчиваться только буквой или цифрой
  • Длина от 1 до 20 символов

Пример

validate("john-doe") → True validate("123user") → False (начинается с цифры) validate("user.") → False (заканчивается точкой)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Валидация логина

Это практическая задача на работу со строками и валидацией данных. Рассмотрю несколько подходов: от простых регулярных выражений до более явных решений.

1. Решение с регулярным выражением

import re

def validate_login(username: str) -> bool:
    """Валидирует логин с использованием регулярного выражения."""
    pattern = r'^[a-zA-Z][a-zA-Z0-9.-]*[a-zA-Z0-9]$|^[a-zA-Z]$'
    return bool(re.match(pattern, username)) and 1 <= len(username) <= 20

Разбор регулярного выражения:

  • ^[a-zA-Z] — начинается с латинской буквы
  • [a-zA-Z0-9.-]* — может содержать буквы, цифры, точки, дефисы (ноль или более)
  • [a-zA-Z0-9]$ — заканчивается буквой или цифрой
  • |^[a-zA-Z]$ — ИЛИ длина 1 символ (одна буква)

Проблемы:

  • Сложно читать и поддерживать
  • Необходимо тестировать отдельно edge-cases

2. Явное решение без регулярных выражений

Этот подход более читаемый и легче тестировать:

def validate_login(username: str) -> bool:
    """Валидирует логин с явной проверкой условий."""
    
    # Проверка длины
    if not (1 <= len(username) <= 20):
        return False
    
    # Проверка первого символа (только буква)
    if not username[0].isalpha():
        return False
    
    # Проверка последнего символа (буква или цифра)
    if not (username[-1].isalnum()):
        return False
    
    # Проверка остальных символов (буквы, цифры, точки, дефисы)
    allowed_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-')
    for char in username:
        if char not in allowed_chars:
            return False
    
    return True

Преимущества:

  • Ясная логика для каждого правила
  • Легче добавлять дополнительные проверки
  • Проще найти проблему при отладке

3. Оптимизированная версия с использованием встроенных функций

def validate_login(username: str) -> bool:
    """Оптимизированная валидация логина."""
    
    # Проверка длины
    if not (1 <= len(username) <= 20):
        return False
    
    # Проверка первого символа
    if not username[0].isalpha():
        return False
    
    # Проверка последнего символа
    if not username[-1].isalnum():
        return False
    
    # Проверка всех символов
    # Разрешённые символы: буквы, цифры, точка, дефис
    return all(
        char.isalnum() or char in '.-'
        for char in username
    )

Улучшения:

  • Использование all() для проверки всех символов
  • Меньше строк кода
  • Сохранение читаемости

4. Класс-валидатор для переиспользования

from dataclasses import dataclass
from typing import Tuple

@dataclass
class LoginValidator:
    """Валидатор логинов с поддержкой детализированных ошибок."""
    min_length: int = 1
    max_length: int = 20
    allowed_special_chars: str = '.-'
    
    def validate(self, username: str) -> Tuple[bool, str]:
        """Возвращает (True/False, сообщение об ошибке)."""
        
        # Проверка длины
        if not username:
            return False, "Логин не может быть пустым"
        
        if len(username) < self.min_length:
            return False, f"Логин слишком короткий (минимум {self.min_length})"
        
        if len(username) > self.max_length:
            return False, f"Логин слишком длинный (максимум {self.max_length})"
        
        # Проверка первого символа
        if not username[0].isalpha():
            return False, "Логин должен начинаться с латинской буквы"
        
        # Проверка последнего символа
        if not username[-1].isalnum():
            return False, "Логин должен заканчиваться буквой или цифрой"
        
        # Проверка остальных символов
        for i, char in enumerate(username):
            if not (char.isalnum() or char in self.allowed_special_chars):
                return False, f"Недопустимый символ '{char}' на позиции {i}"
        
        return True, "OK"


# Использование
validator = LoginValidator()
is_valid, message = validator.validate("john-doe")
print(f"Валиден: {is_valid}, сообщение: {message}")

5. Полное тестирование

import unittest

class TestLoginValidator(unittest.TestCase):
    
    def setUp(self):
        self.validate = validate_login
    
    # Позитивные тесты
    def test_simple_username(self):
        self.assertTrue(self.validate("a"))
    
    def test_username_with_digits(self):
        self.assertTrue(self.validate("user123"))
    
    def test_username_with_dash(self):
        self.assertTrue(self.validate("john-doe"))
    
    def test_username_with_dot(self):
        self.assertTrue(self.validate("john.doe"))
    
    def test_username_mixed(self):
        self.assertTrue(self.validate("User.Name-123"))
    
    def test_max_length(self):
        self.assertTrue(self.validate("a" * 20))
    
    # Негативные тесты
    def test_empty_username(self):
        self.assertFalse(self.validate(""))
    
    def test_too_long(self):
        self.assertFalse(self.validate("a" * 21))
    
    def test_starts_with_digit(self):
        self.assertFalse(self.validate("123user"))
    
    def test_starts_with_dash(self):
        self.assertFalse(self.validate("-username"))
    
    def test_starts_with_dot(self):
        self.assertFalse(self.validate(".username"))
    
    def test_ends_with_dash(self):
        self.assertFalse(self.validate("username-"))
    
    def test_ends_with_dot(self):
        self.assertFalse(self.validate("user."))
    
    def test_invalid_special_char(self):
        self.assertFalse(self.validate("user@name"))
    
    def test_space_in_username(self):
        self.assertFalse(self.validate("user name"))
    
    def test_uppercase(self):
        self.assertTrue(self.validate("UserName"))
    
    def test_consecutive_special_chars(self):
        self.assertTrue(self.validate("user.-name"))

if __name__ == '__main__':
    unittest.main()

6. Сравнение решений

ПодходЧитаемостьГибкостьСкоростьРекомендация
Regex2/53/5ОтличноДля простых случаев
Явный код5/55/5Хорошо✅ Лучший вариант
All()4/54/5ОтличноДля опытных
Класс-валидатор5/55/5ХорошоДля production

7. Производственное решение

from typing import Dict, Optional

class UserValidator:
    """Валидатор для реального приложения."""
    
    # Регулярное выражение как константа
    LOGIN_PATTERN = r'^[a-zA-Z][a-zA-Z0-9.-]{0,18}[a-zA-Z0-9]$|^[a-zA-Z]$'
    
    @classmethod
    def validate_login(cls, username: str, detailed: bool = False) -> Dict[str, any]:
        """Валидирует логин с опциональными деталями ошибок."""
        
        errors = []
        
        # Основные проверки
        if not username:
            errors.append("Логин не может быть пустым")
            return {"valid": False, "errors": errors} if detailed else {"valid": False}
        
        if len(username) > 20:
            errors.append(f"Логин не должен превышать 20 символов (текущая длина: {len(username)})")
        
        if len(username) < 1:
            errors.append("Логин должен содержать хотя бы 1 символ")
        
        if not username[0].isalpha():
            errors.append("Логин должен начинаться с латинской буквы")
        
        if len(username) > 1 and not username[-1].isalnum():
            errors.append("Логин должен заканчиваться буквой или цифрой")
        
        for char in username:
            if not (char.isalnum() or char in '.-'):
                errors.append(f"Недопустимый символ: '{char}'")
                break
        
        result = {"valid": len(errors) == 0}
        if detailed and errors:
            result["errors"] = errors
        
        return result

Вывод

Для валидации логина рекомендую явное решение с использованием простых условий:

  • Максимальная ясность кода
  • Легко добавлять новые правила
  • Удобно отладить и тестировать
  • Не требует знания регулярных выражений

Для production-кода используйте класс-валидатор с детализированными сообщениями об ошибках.

Валидация логина | PrepBro