前言

大家好,这里是 Charming讲Python编码小技巧 系列专栏。每天分享一个 30-seconds-of-python 仓库中的神级写法,助你告别"屎山"代码,写出让人眼前一亮的 Pythonic 风格!

小技巧内容描述

在Python开发中,你是否遇到过这样的场景:需要将驼峰命名转换为蛇形命名?比如将camelCase转换为camel_case,将SomeMixedString转换为some_mixed_string

还在手动替换大小写?今天教你一个超实用的技巧,几行代码就能搞定!

from re import sub

def snake(s):
    return '_'.join(
        sub('([A-Z][a-z]+)', r' \1',
        sub('([A-Z]+)', r' \1',
        s.replace('-', ' '))).split()).lower()

就这么简单!让我来详细解释一下为什么这么写。

为什么这么用?

核心原理:正则表达式的威力

这个技巧的核心在于使用正则表达式对字符串进行"智能分割":

  1. s.replace('-', ' ') - 先将连字符替换为空格,统一分隔符
  2. sub('([A-Z]+)', r' \1', ...) - 在大写字母前插入空格(处理连续大写)
  3. sub('([A-Z][a-z]+)', r' \1', ...) - 在大写字母开头的单词前插入空格
  4. .split() - 按空格分割成单词列表
  5. '_'.join(...).lower() - 用下划线连接并转为小写

举个栗子

# 基础示例
print(snake('camelCase'))              # 'camel_case'
print(snake('some text'))              # 'some_text'
print(snake('AllThe-small Things'))    # 'all_the_small_things'

# 处理复杂混合字符串
print(snake('some-mixed_string With spaces_underscores-and-hyphens'))
# 'some_mixed_string_with_spaces_underscores_and_hyphens'

# 处理连续大写
print(snake('XMLHttpRequest'))         # 'xml_http_request'
print(snake('getURLById'))             # 'get_url_by_id'

# 处理数字
print(snake('user2Name'))              # 'user2_name'
print(snake('API2Version3'))           # 'api2_version3'

更多实用场景

场景一:API接口参数转换

# 假设从前端接收到的参数是驼峰命名
def process_user_data(data):
    """处理用户数据,将驼峰命名转换为蛇形命名"""
    converted = {}
    for key, value in data.items():
        converted[snake(key)] = value
    return converted

# 测试
user_input = {
    'userName': '张三',
    'userAge': 25,
    'userEmail': 'zhangsan@example.com',
    'phoneNumber': '13800138000'
}

processed = process_user_data(user_input)
print(processed)
# 输出: {'user_name': '张三', 'user_age': 25, 'user_email': 'zhangsan@example.com', 'phone_number': '13800138000'}

场景二:数据库字段映射

# 将Python类属性名映射到数据库字段名
class User:
    def __init__(self):
        self.userId = None
        self.userName = None
        self.userEmail = None
        self.createdAt = None
        self.updatedAt = None

def get_db_field_names(model_class):
    """获取模型类对应的数据库字段名"""
    return [snake(attr) for attr in dir(model_class) if not attr.startswith('_')]

# 测试
print(get_db_field_names(User))
# 输出: ['user_id', 'user_name', 'user_email', 'created_at', 'updated_at']

场景三:JSON数据规范化

import json

def normalize_json_keys(data):
    """规范化JSON数据的键名"""
    if isinstance(data, dict):
        return {snake(k): normalize_json_keys(v) for k, v in data.items()}
    elif isinstance(data, list):
        return [normalize_json_keys(item) for item in data]
    else:
        return data

# 测试
api_response = {
    'userId': 123,
    'userName': '张三',
    'userProfile': {
        'firstName': '三',
        'lastName': '张',
        'contactInfo': {
            'phoneNumber': '13800138000',
            'emailAddress': 'zhangsan@example.com'
        }
    },
    'orderHistory': [
        {'orderId': 1, 'orderDate': '2024-01-01'},
        {'orderId': 2, 'orderDate': '2024-01-15'}
    ]
}

normalized = normalize_json_keys(api_response)
print(json.dumps(normalized, ensure_ascii=False, indent=2))
# 输出:
# {
#   "user_id": 123,
#   "user_name": "张三",
#   "user_profile": {
#     "first_name": "三",
#     "last_name": "张",
#     "contact_info": {
#       "phone_number": "13800138000",
#       "email_address": "zhangsan@example.com"
#     }
#   },
#   "order_history": [
#     {"order_id": 1, "order_date": "2024-01-01"},
#     {"order_id": 2, "order_date": "2024-01-15"}
#   ]
# }

场景四:类属性自动转换

class Base:
    """基类,自动处理属性命名转换"""
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, snake(key), value)

    def to_dict(self):
        """将对象转换为字典"""
        return {snake(k): v for k, v in self.__dict__.items()}

class User(Base):
    pass

# 测试
user = User(userName='张三', userEmail='zhangsan@example.com', userAge=25)
print(user.user_name)  # '张三'
print(user.user_email)  # 'zhangsan@example.com'
print(user.user_age)  # 25
print(user.to_dict())  # {'user_name': '张三', 'user_email': 'zhangsan@example.com', 'user_age': 25}

场景五:代码生成器

def generate_python_class(json_schema, class_name):
    """根据JSON Schema生成Python类"""
    fields = []
    for field_name, field_type in json_schema.items():
        snake_name = snake(field_name)
        fields.append(f"    self.{snake_name}: {field_type} = None")

    class_code = f'''class {class_name}:
    """自动生成的{class_name}类"""

    def __init__(self):
{chr(10).join(fields)}

    def to_dict(self):
        return {{k: v for k, v in self.__dict__.items() if v is not None}}
'''
    return class_code

# 测试
schema = {
    'userId': 'int',
    'userName': 'str',
    'userEmail': 'str',
    'createdAt': 'str',
    'updatedAt': 'str'
}

print(generate_python_class(schema, 'User'))
# 输出:
# class User:
#     """自动生成的User类"""
#
#     def __init__(self):
#         self.user_id: int = None
#         self.user_name: str = None
#         self.user_email: str = None
#         self.created_at: str = None
#         self.updated_at: str = None
#
#     def to_dict(self):
#         return {k: v for k, v in self.__dict__.items() if v is not None}

对比其他方法

方法1:手动替换(繁琐)

def snake_manual(s):
    """手动替换,容易出错"""
    result = []
    for i, char in enumerate(s):
        if char.isupper():
            if i > 0 and (s[i-1].islower() or (i+1 < len(s) and s[i+1].islower())):
                result.append('_')
            result.append(char.lower())
        elif char == '-':
            result.append('_')
        else:
            result.append(char)
    return ''.join(result)

print(snake_manual('camelCase'))  # 'camel_case'
print(snake_manual('XMLHttpRequest'))  # 'xml_http_request'

方法2:使用第三方库(依赖外部)

# 需要安装: pip install inflection
import inflection

print(inflection.underscore('camelCase'))  # 'camel_case'
print(inflection.underscore('XMLHttpRequest'))  # 'xml_http_request'

我们的版本(推荐)

from re import sub

def snake(s):
    return '_'.join(
        sub('([A-Z][a-z]+)', r' \1',
        sub('([A-Z]+)', r' \1',
        s.replace('-', ' '))).split()).lower()

对比总结:

  • 手动替换:代码冗长,逻辑复杂,容易出错
  • 第三方库:功能强大,但需要额外依赖
  • 我们的版本:简洁高效,零依赖,功能完整

进阶版本:处理更多边缘情况

from re import sub

def snake_advanced(s, *, uppercase=False):
    """高级版本的snake,支持更多选项"""
    # 基础处理
    result = '_'.join(
        sub('([A-Z][a-z]+)', r' \1',
        sub('([A-Z]+)', r' \1',
        s.replace('-', ' '))).split())

    # 转换大小写
    return result.upper() if uppercase else result.lower()

# 测试
print(snake_advanced('camelCase'))                    # 'camel_case'
print(snake_advanced('camelCase', uppercase=True))   # 'CAMEL_CASE'
print(snake_advanced('XMLHttpRequest'))              # 'xml_http_request'
print(snake_advanced('API2Version3'))                # 'api2_version3'

性能对比

import time
import inflection

# 测试字符串
test_strings = [
    'camelCase',
    'some text',
    'AllThe-small Things',
    'XMLHttpRequest',
    'getURLById',
    'API2Version3',
    'some-mixed_string With spaces_underscores-and-hyphens'
]

# 测试我们的方法
start = time.time()
for _ in range(10000):
    for s in test_strings:
        snake(s)
print(f"我们的方法: {time.time() - start:.4f}秒")

# 测试第三方库方法
start = time.time()
for _ in range(10000):
    for s in test_strings:
        inflection.underscore(s)
print(f"第三方库方法: {time.time() - start:.4f}秒")

总结

这个"驼峰命名转蛇形命名"的技巧之所以如此实用,是因为:

  1. 代码简洁:核心逻辑只有几行正则表达式
  2. 功能完整:处理了驼峰、连字符、空格等各种情况
  3. 零依赖:不需要安装任何第三方库
  4. 通用性强:适用于各种命名转换场景
  5. 符合规范:Python官方推荐的命名风格

命名规范小贴士

  1. Python命名规范(PEP 8)

    • 变量/函数:使用蛇形命名 my_variable
    • 类名:使用驼峰命名 MyClass
    • 常量:使用全大写 MY_CONSTANT
  2. 何时使用蛇形命名

    • Python变量和函数
    • 数据库字段名
    • JSON键名(Python风格)
    • 配置文件键名
  3. 何时使用驼峰命名

    • 类名
    • JavaScript/Java变量
    • API接口参数(某些风格)

今日小技巧: snake() - 一行代码转蛇形命名

适用场景: API参数转换、数据库字段映射、JSON规范化、代码生成等

点赞收藏,每天学一个Python神技巧!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐