Фаза 2: config.py, checksum.py, random_utils.py, generators/ (11 модулей)
Deploy drhider / validate (push) Waiting to run
Deploy drhider / validate (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Контрольные суммы для ИНН (10 и 12 знаков) и ОГРН.
|
||||
|
||||
Используются генераторами фиктивных значений для создания
|
||||
синтаксически корректных номеров.
|
||||
"""
|
||||
|
||||
|
||||
def checksum_inn10(inn: str) -> str:
|
||||
"""Контрольная сумма для 10-значного ИНН (юридические лица).
|
||||
|
||||
Алгоритм:
|
||||
1. Умножаем первые 9 цифр на коэффициенты [2,4,10,3,5,9,4,6,8]
|
||||
2. Суммируем, берём (сумма % 11) % 10
|
||||
|
||||
Args:
|
||||
inn: 9 цифр ИНН (без контрольной суммы)
|
||||
|
||||
Returns:
|
||||
Одна цифра контрольной суммы
|
||||
"""
|
||||
coeffs = [2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
s = sum(int(inn[i]) * coeffs[i] for i in range(9))
|
||||
return str((s % 11) % 10)
|
||||
|
||||
|
||||
def checksum_inn12(inn: str) -> str:
|
||||
"""Контрольные суммы для 12-значного ИНН (физические лица).
|
||||
|
||||
Алгоритм (две контрольные цифры):
|
||||
1. n1: умножаем первые 10 цифр на [7,2,4,10,3,5,9,4,6,8],
|
||||
берём (сумма % 11) % 10
|
||||
2. n2: добавляем n1 к числу, умножаем 11 цифр на
|
||||
[3,7,2,4,10,3,5,9,4,6,8], берём (сумма % 11) % 10
|
||||
|
||||
Args:
|
||||
inn: 10 цифр ИНН (без контрольных сумм)
|
||||
|
||||
Returns:
|
||||
Две цифры контрольных сумм (n1 + n2)
|
||||
"""
|
||||
c1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
c2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
|
||||
|
||||
# Первая контрольная цифра
|
||||
s1 = sum(int(inn[i]) * c1[i] for i in range(10))
|
||||
n1 = (s1 % 11) % 10
|
||||
|
||||
# Вторая контрольная цифра (с учётом первой)
|
||||
inn2 = inn + str(n1)
|
||||
s2 = sum(int(inn2[i]) * c2[i] for i in range(11))
|
||||
n2 = (s2 % 11) % 10
|
||||
|
||||
return str(n1) + str(n2)
|
||||
|
||||
|
||||
def checksum_ogrn(ogrn: str) -> str:
|
||||
"""Контрольная сумма для ОГРН (13 знаков).
|
||||
|
||||
Алгоритм: первые 12 цифр как число % 11, затем % 10.
|
||||
|
||||
Args:
|
||||
ogrn: 12 цифр ОГРН (без контрольной суммы)
|
||||
|
||||
Returns:
|
||||
Одна цифра контрольной суммы
|
||||
"""
|
||||
s = int(ogrn) % 11
|
||||
return str(s % 10)
|
||||
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Константы и словари для DrHider.
|
||||
|
||||
Содержит:
|
||||
- ENTITY_PATTERNS — regex-паттерны для обнаружения сущностей
|
||||
- COMPANY_PATTERN — паттерн для обнаружения названий компаний
|
||||
- PERSON_PATTERN — паттерн для обнаружения ФИО
|
||||
- Словари русских имён, городов, улиц
|
||||
- Список фиктивных доменов для email
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Regex-паттерны для обнаружения сущностей в тексте документов
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ENTITY_PATTERNS = {
|
||||
"phone": r'(?<!\d)(?:\+7|8)[\s\-]?\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}(?!\d)',
|
||||
"email": r'\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b',
|
||||
# 12-значный ИНН ДО 10-значного — иначе 12-значный матчится как 10-значный
|
||||
"inn_fl": r'ИНН\s*\d{12}',
|
||||
"inn_ul": r'ИНН\s*\d{10}',
|
||||
"ogrn": r'ОГРН\s*\d{13}',
|
||||
"kpp": r'КПП\s*\d{9}',
|
||||
"bik": r'БИК\s*\d{9}',
|
||||
"rs": r'(?:р/с|расч[её]тный\s*сч[её]т)\s*\d{20}',
|
||||
"ks": r'(?:к/с|кор/сч|корр?[еи]?спондентский\s*сч[её]т)\s*\d{20}',
|
||||
"passport": r'(?:паспорт|серия\s+номер)[\s:№]*\d{2}\s*\d{2}\s*\d{6,7}',
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Паттерны для LLM-независимого обнаружения (regex)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Названия компаний: ООО/ЗАО/АО/ПАО/ИП/ТОО + название в кавычках или без
|
||||
COMPANY_PATTERN = re.compile(
|
||||
r'(?:ООО|ЗАО|ОАО|АО|ПАО|ИП|ТОО)\s+(?:«[^»]+»|"[^"]+"|\u201c[^\u201d]+\u201d|[А-ЯA-Z][\w\-\.]{2,40})',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# ФИО: Фамилия + инициалы (Иванов И.И.) или полное имя (Иванов Иван Иванович)
|
||||
PERSON_PATTERN = re.compile(
|
||||
r'\b[А-Я][а-я]+\s+[А-Я]\.[А-Я]\.'
|
||||
r'|\b[А-Я][а-я]+\s+[А-Я][а-я]+\s+[А-Я][а-я]+',
|
||||
)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Словари для генерации фиктивных значений
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Русские фамилии (20 самых распространённых)
|
||||
RU_SURNAMES = [
|
||||
"Иванов", "Смирнов", "Кузнецов", "Попов", "Васильев", "Петров",
|
||||
"Соколов", "Михайлов", "Новиков", "Фёдоров", "Морозов", "Волков",
|
||||
"Алексеев", "Лебедев", "Семёнов", "Егоров", "Павлов", "Козлов",
|
||||
"Степанов", "Николаев",
|
||||
]
|
||||
|
||||
# Русские мужские имена
|
||||
RU_NAMES = [
|
||||
"Александр", "Дмитрий", "Сергей", "Андрей", "Алексей", "Максим",
|
||||
"Евгений", "Иван", "Михаил", "Николай", "Владимир", "Павел",
|
||||
"Виктор", "Олег",
|
||||
]
|
||||
|
||||
# Русские отчества
|
||||
RU_PATRONYMICS = [
|
||||
"Александрович", "Дмитриевич", "Сергеевич", "Андреевич",
|
||||
"Алексеевич", "Иванович", "Михайлович", "Николаевич", "Владимирович",
|
||||
"Павлович", "Викторович", "Олегович", "Евгеньевич", "Максимович",
|
||||
]
|
||||
|
||||
# Города России (15 крупнейших)
|
||||
RU_CITIES = [
|
||||
"Москва", "Санкт-Петербург", "Новосибирск", "Екатеринбург",
|
||||
"Казань", "Нижний Новгород", "Челябинск", "Самара", "Омск",
|
||||
"Ростов-на-Дону", "Уфа", "Красноярск", "Воронеж", "Пермь", "Волгоград",
|
||||
]
|
||||
|
||||
# Названия улиц
|
||||
RU_STREETS = [
|
||||
"Ленина", "Мира", "Пушкина", "Гагарина", "Советская",
|
||||
"Кирова", "Октябрьская", "Молодёжная", "Садовая", "Центральная",
|
||||
]
|
||||
|
||||
# Фиктивные домены для генерации email
|
||||
FAKE_DOMAINS = ["example.ru", "mail.test", "company.local", "org.example.ru"]
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Маппинг типов сущностей на генераторы фиктивных значений.
|
||||
|
||||
ENTITY_GENERATORS используется в TwoPassObfuscator._scan_regex()
|
||||
для автоматического выбора генератора по типу найденной сущности.
|
||||
"""
|
||||
|
||||
from .phone import generate_phone
|
||||
from .email import generate_email
|
||||
from .inn import generate_inn10, generate_inn12
|
||||
from .ogrn import generate_ogrn
|
||||
from .kpp import generate_kpp
|
||||
from .bik import generate_bik
|
||||
from .accounts import generate_rs, generate_ks
|
||||
from .passport import generate_passport
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Маппинг: entity_type → функция-генератор
|
||||
# Ключи соответствуют ключам в config.ENTITY_PATTERNS
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
ENTITY_GENERATORS = {
|
||||
"phone": generate_phone,
|
||||
"email": generate_email,
|
||||
"inn_ul": generate_inn10, # 10-значный ИНН → юрлица
|
||||
"inn_fl": generate_inn12, # 12-значный ИНН → физлица
|
||||
"ogrn": generate_ogrn,
|
||||
"kpp": generate_kpp,
|
||||
"bik": generate_bik,
|
||||
"rs": generate_rs, # расчётный счёт
|
||||
"ks": generate_ks, # корреспондентский счёт
|
||||
"passport": generate_passport,
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Генераторы фиктивных банковских счетов.
|
||||
|
||||
- generate_rs: расчётный счёт (20 знаков)
|
||||
- generate_ks: корреспондентский счёт (20 знаков)
|
||||
|
||||
Счета начинаются с реальных префиксов: 40702 (расчётный), 30101 (корреспондентский).
|
||||
"""
|
||||
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
|
||||
|
||||
def generate_rs(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 20-значный расчётный счёт.
|
||||
|
||||
Сохраняет оригинальный префикс (р/с, расчётный счёт и т.д.).
|
||||
Начинается с 40702 (реальный префикс расчётного счёта).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с расчётным счётом
|
||||
|
||||
Returns:
|
||||
Строка вида «р/с 40702XXXXXXXXXXXXXXX»
|
||||
"""
|
||||
prefix = re.match(r'(?:р/с|расч[её]тный\s*сч[её]т)\s*', original, re.IGNORECASE).group(0)
|
||||
return prefix + "40702" + random_digits(15)
|
||||
|
||||
|
||||
def generate_ks(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 20-значный корреспондентский счёт.
|
||||
|
||||
Сохраняет оригинальный префикс (к/с, кор/счёт и т.д.).
|
||||
Начинается с 30101 (реальный префикс корр. счёта).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с корр. счётом
|
||||
|
||||
Returns:
|
||||
Строка вида «к/с 30101XXXXXXXXXXXXXXX»
|
||||
"""
|
||||
prefix = re.match(
|
||||
r'(?:к/с|кор/сч|корр?[еи]?спондентский\s*сч[её]т)\s*',
|
||||
original,
|
||||
re.IGNORECASE,
|
||||
).group(0)
|
||||
return prefix + "30101" + random_digits(15)
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Генератор фиктивных почтовых адресов.
|
||||
|
||||
Формат: Город, ул. Улица, д. Номер
|
||||
"""
|
||||
|
||||
import random
|
||||
from ..config import RU_CITIES, RU_STREETS
|
||||
|
||||
|
||||
def generate_address(_: str) -> str:
|
||||
"""Сгенерировать фиктивный почтовый адрес.
|
||||
|
||||
Выбирает случайный город, улицу и номер дома из словарей.
|
||||
|
||||
Args:
|
||||
_: Оригинальный адрес (игнорируется)
|
||||
|
||||
Returns:
|
||||
Строка вида «Москва, ул. Ленина, д. 42»
|
||||
"""
|
||||
city = random.choice(RU_CITIES)
|
||||
street = random.choice(RU_STREETS)
|
||||
house = random.randint(1, 200)
|
||||
return f"{city}, ул. {street}, д. {house}"
|
||||
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
Генератор фиктивного БИК (9 знаков).
|
||||
|
||||
Формат: «БИК » + 9 цифр.
|
||||
Первые 2 цифры — 04 (код РФ).
|
||||
"""
|
||||
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
|
||||
|
||||
def generate_bik(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 9-значный БИК.
|
||||
|
||||
Сохраняет оригинальный префикс (например, «БИК »).
|
||||
Начинается с 04 (код Российской Федерации).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с БИК
|
||||
|
||||
Returns:
|
||||
Строка вида «БИК 04XXXXXXX»
|
||||
"""
|
||||
prefix = re.match(r'БИК\s*', original, re.IGNORECASE).group(0)
|
||||
return prefix + "04" + random_digits(7)
|
||||
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Генератор фиктивных названий компаний.
|
||||
|
||||
Формат: ООО/ЗАО/АО «СлучайноеСуществительное»
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def generate_company(_: str) -> str:
|
||||
"""Сгенерировать фиктивное название компании.
|
||||
|
||||
Выбирает случайную организационно-правовую форму (ООО, ЗАО, АО)
|
||||
и случайное существительное из словаря.
|
||||
|
||||
Args:
|
||||
_: Оригинальное название (игнорируется)
|
||||
|
||||
Returns:
|
||||
Строка вида «ООО «Технология»»
|
||||
"""
|
||||
forms = ["ООО", "ЗАО", "АО"]
|
||||
nouns = [
|
||||
"Технология", "Прогресс", "Гарант", "Стандарт", "Импульс",
|
||||
"Вектор", "Сфера", "Альянс", "Синтез", "Меридиан", "Спектр", "Формат",
|
||||
]
|
||||
return f'{random.choice(forms)} «{random.choice(nouns)}»'
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Генератор фиктивного email-адреса.
|
||||
|
||||
Сохраняет структуру оригинального email: локальная_часть@домен.
|
||||
"""
|
||||
|
||||
import random
|
||||
from ..config import FAKE_DOMAINS
|
||||
from ..random_utils import random_letters
|
||||
|
||||
|
||||
def generate_email(original: str) -> str:
|
||||
"""Сгенерировать фиктивный email с тем же форматом.
|
||||
|
||||
Args:
|
||||
original: Оригинальный email (для сохранения структуры, не используется)
|
||||
|
||||
Returns:
|
||||
Фиктивный email вида случайные_буквы@фиктивный_домен
|
||||
"""
|
||||
domain = random.choice(FAKE_DOMAINS)
|
||||
local = random_letters(random.randint(5, 10))
|
||||
return f"{local}@{domain}"
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Генераторы фиктивных ИНН.
|
||||
|
||||
- generate_inn10: 10-значный ИНН (юридические лица)
|
||||
- generate_inn12: 12-значный ИНН (физические лица)
|
||||
|
||||
Оба сохраняют префикс «ИНН » и генерируют корректную контрольную сумму.
|
||||
"""
|
||||
|
||||
import random
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
from ..checksum import checksum_inn10, checksum_inn12
|
||||
|
||||
|
||||
def generate_inn10(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 10-значный ИНН (для юридических лиц).
|
||||
|
||||
Сохраняет оригинальный префикс (например, «ИНН »).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с ИНН
|
||||
|
||||
Returns:
|
||||
Строка вида «ИНН XXXXXXXXX-C» с корректной контрольной суммой
|
||||
"""
|
||||
prefix = re.match(r'ИНН\s*', original, re.IGNORECASE).group(0)
|
||||
base = f"{random.randint(1,9)}{random_digits(8)}"
|
||||
return prefix + base + checksum_inn10(base)
|
||||
|
||||
|
||||
def generate_inn12(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 12-значный ИНН (для физических лиц).
|
||||
|
||||
Сохраняет оригинальный префикс (например, «ИНН »).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с ИНН
|
||||
|
||||
Returns:
|
||||
Строка вида «ИНН XXXXXXXXXX-CC» с двумя корректными контрольными суммами
|
||||
"""
|
||||
prefix = re.match(r'ИНН\s*', original, re.IGNORECASE).group(0)
|
||||
base = f"{random.randint(1,9)}{random_digits(9)}"
|
||||
return prefix + base + checksum_inn12(base)
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Генератор фиктивного КПП (9 знаков).
|
||||
|
||||
Формат: «КПП » + 9 цифр.
|
||||
Последние 3 цифры — код причины постановки (01, 43 или 77).
|
||||
"""
|
||||
|
||||
import random
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
|
||||
|
||||
def generate_kpp(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 9-значный КПП.
|
||||
|
||||
Сохраняет оригинальный префикс (например, «КПП »).
|
||||
Последние 3 цифры — один из реальных кодов причины постановки.
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с КПП
|
||||
|
||||
Returns:
|
||||
Строка вида «КПП XXXX-код-XXX»
|
||||
"""
|
||||
prefix = re.match(r'КПП\s*', original, re.IGNORECASE).group(0)
|
||||
return prefix + random_digits(4) + random.choice(["01", "43", "77"]) + random_digits(3)
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Генератор фиктивного ОГРН (13 знаков).
|
||||
|
||||
Формат: «ОГРН » + 13 цифр с корректной контрольной суммой.
|
||||
"""
|
||||
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
from ..checksum import checksum_ogrn
|
||||
|
||||
|
||||
def generate_ogrn(original: str) -> str:
|
||||
"""Сгенерировать фиктивный 13-значный ОГРН.
|
||||
|
||||
Сохраняет оригинальный префикс (например, «ОГРН »).
|
||||
Первая цифра всегда 1 (признак юридического лица).
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с ОГРН
|
||||
|
||||
Returns:
|
||||
Строка вида «ОГРН 1XXXXXXXXXXX-C» с корректной контрольной суммой
|
||||
"""
|
||||
prefix = re.match(r'ОГРН\s*', original, re.IGNORECASE).group(0)
|
||||
base = "1" + random_digits(11)
|
||||
return prefix + base + checksum_ogrn(base)
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Генератор фиктивных паспортных данных.
|
||||
|
||||
Формат: «паспорт » / «серия номер » + XX XX XXXXXX
|
||||
"""
|
||||
|
||||
import random
|
||||
import re
|
||||
from ..random_utils import random_digits
|
||||
|
||||
|
||||
def generate_passport(original: str) -> str:
|
||||
"""Сгенерировать фиктивные паспортные данные.
|
||||
|
||||
Сохраняет оригинальный префикс (паспорт, серия номер и т.д.).
|
||||
Формат: 2 цифры серии + 2 цифры + 6 цифр номера.
|
||||
|
||||
Args:
|
||||
original: Оригинальная строка с паспортными данными
|
||||
|
||||
Returns:
|
||||
Строка вида «паспорт XX XX XXXXXX»
|
||||
"""
|
||||
m = re.match(r'(?:паспорт|серия\s+номер)[\s:№]*', original, re.IGNORECASE)
|
||||
prefix = m.group(0) if m else ""
|
||||
return prefix + f"{random.randint(10, 99)} {random.randint(10, 99)} {random_digits(6)}"
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Генератор фиктивных ФИО.
|
||||
|
||||
Формат: Фамилия И.О. (фамилия полностью, имя и отчество — инициалы).
|
||||
"""
|
||||
|
||||
import random
|
||||
from ..config import RU_SURNAMES, RU_NAMES, RU_PATRONYMICS
|
||||
|
||||
|
||||
def generate_person(_: str) -> str:
|
||||
"""Сгенерировать фиктивное ФИО в формате «Фамилия И.О.».
|
||||
|
||||
Выбирает случайные фамилию, имя и отчество из словарей.
|
||||
Имя и отчество сокращаются до инициалов.
|
||||
|
||||
Args:
|
||||
_: Оригинальное ФИО (игнорируется)
|
||||
|
||||
Returns:
|
||||
Строка вида «Иванов А.И.»
|
||||
"""
|
||||
s = random.choice(RU_SURNAMES)
|
||||
n = random.choice(RU_NAMES)
|
||||
p = random.choice(RU_PATRONYMICS)
|
||||
return f"{s} {n[0]}.{p[0]}."
|
||||
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
Генератор фиктивного телефонного номера.
|
||||
|
||||
Формат: +7 (код) XXX-XX-XX
|
||||
Код выбирается случайно из списка реальных российских кодов.
|
||||
"""
|
||||
|
||||
import random
|
||||
from ..random_utils import random_digits
|
||||
|
||||
|
||||
def generate_phone(_: str) -> str:
|
||||
"""Сгенерировать фиктивный российский телефонный номер.
|
||||
|
||||
Args:
|
||||
_: Оригинальный номер (игнорируется, нужен для единого интерфейса)
|
||||
|
||||
Returns:
|
||||
Строка в формате +7 (XXX) XXX-XX-XX
|
||||
"""
|
||||
code = random.choice(["495", "499", "812", "383", "343"])
|
||||
return f"+7 ({code}) {random_digits(3)}-{random_digits(2)}-{random_digits(2)}"
|
||||
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
Утилиты для генерации случайных строк.
|
||||
|
||||
Используются генераторами фиктивных значений для создания
|
||||
случайных цифр и букв в номерах/реквизитах.
|
||||
"""
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def random_digits(n: int) -> str:
|
||||
"""Сгенерировать строку из n случайных цифр.
|
||||
|
||||
Args:
|
||||
n: Количество цифр
|
||||
|
||||
Returns:
|
||||
Строка из n случайных цифр (0-9)
|
||||
"""
|
||||
return ''.join(random.choice(string.digits) for _ in range(n))
|
||||
|
||||
|
||||
def random_letters(n: int) -> str:
|
||||
"""Сгенерировать строку из n случайных строчных латинских букв.
|
||||
|
||||
Args:
|
||||
n: Количество букв
|
||||
|
||||
Returns:
|
||||
Строка из n случайных букв (a-z)
|
||||
"""
|
||||
return ''.join(random.choice(string.ascii_lowercase) for _ in range(n))
|
||||
Reference in New Issue
Block a user