Разбор полетов: как правильно убивать верблюдов 🐪

В рамках универсального перевода строки в snake_case такой подход с регулярками, наверное, самый хороший вариант:

import re

def to_snake_case(s: str) -> str:
s = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s).replace('-', '_').lower()


🧐 Как это работает?

1️⃣ re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s) заменяет каждую заглавную букву, предшествующую строчной, на ту же букву, но с подчеркиванием перед ней.

2️⃣ re.sub('([a-z0-9])([A-Z])', r'\1_\2', s) меняет каждую заглавную букву, предшествующую строчной или цифре, на эту же букву, но с подчеркиванием перед ней.

3️⃣ Потом всё приводим к нижнему регистру и заменяем дефис на подчеркивание.

Но давайте выйдем за рамки алгозадачи и посмотрим, как такую проблему решать в реальности. Если в проекте используется Pydantic, то там всё это давно написано, протестировано на миллионах строк кода и лежит прямо в коробке:

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel, to_snake

class FrontendData(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel, # Автоматом ждет camelCase на вход
populate_by_name=True
)

python_talk: str # В коде работаем с православным snake_case

Под капотом лежат вылизанные алгоритмы, которые учитывают краевые случаи, нестандартные разделители и больные фантазии разработчиков смежных систем.

А если Pydantic в проекте нет, есть микро-библиотеки вроде inflection, которые делают то же самое:
import inflection

inflection.underscore("CamelCaseAnd-kebab-case")
# -> 'camel_case_and_kebab_case'

inflection.camelize("python_talk", uppercase_first_letter=False)
# -> 'pythonTalk'


Алгоритмы на регулярках знать полезно, чтобы пройти алгособес и понимать, как парсятся строки. А вот инструменты знать необходимо, чтобы не тащить в прод самописные велосипеды, которые неминуемо сломаются на первом же кривом payload'е.

#алгособес