为什么Python老手都用f-string?看完我悟了

36 阅读6分钟

你有没有写过这样的代码:

name = "张三"
age = 25
city = "北京"

# 很多人这样写
result = "姓名:" + name + ",年龄:" + str(age) + ",城市:" + city

然后代码review的时候,老大看了三秒,说了句:"你知道现在都2024年了吗?"

你愣住了。我也愣住了。

直到后来我发现,Python老手写字符串拼接,用的都是这种骚操作...

从痛苦到解脱:字符串格式化的进化史

第一阶段:原始社会的+号拼接

就像我刚学Python时那样,很多人用+号:

# ❌ 别这么写了,求你了
first_name = "小明"
last_name = "王"
full_name = first_name + last_name  # 王小明

# 看起来还行,对吧?但是遇到数字就懵了
age = 18
message = "我今年" + age + "岁"  # TypeError: can only concatenate str (not "int") to str

痛点有多痛:

  • 手动转类型:str(age)
  • 代码像意大利面,可读性差
  • 性能差(每次拼接都创建新字符串对象)

第二阶段:%格式化(听起来很老对吧)

# ✅ 比+号强一点,但依然丑陋
name = "李四"
age = 30
message = "我叫%s,今年%d岁" % (name, age)

# 如果参数多了...
message = "我叫%s,今年%d岁,住在%s,月薪%d元" % (name, age, city, salary)
# 这里的参数顺序千万别搞错,不然就等着debug吧

依然存在的问题:

  • 顺序敏感,容易搞混
  • %s、%d、%f...记不住啊
  • 不支持字典变量,很难维护

第三阶段:str.format()(曾经过渡的王者)

# ✅ 这么写已经很不错了
name = "王五"
age = 28
message = "我叫{},今年{}岁".format(name, age)

# 还可以指定位置
message = "我叫{0},今年{1}岁,{0}你好".format(name, age)

# 甚至可以用关键字参数
message = "我叫{name},今年{age}岁".format(name=name, age=age)

format()的好处:

  • 顺序不敏感
  • 支持关键字参数,可读性up
  • 支持复杂的格式化选项

但...依然有点啰嗦,对吧?

第四阶段:f-string(真香警告)

# ✅ 终极形态,简洁到爆炸
name = "赵六"
age = 32
message = f"我叫{name},今年{age}岁"  # 就这么简单!

# 还能直接写表达式
score = 85
message = f"考试得分:{score}{'及格' if score >= 60 else '挂科'}"

# 甚至可以调用函数
def get_status(score):
    return "优秀" if score >= 90 else "良好" if score >= 80 else "及格"
message = f"评级:{get_status(score)}"

f-string的优雅之处:

  • 语法简洁,f"{}"一眼就懂
  • 变量直接嵌入,不用记顺序
  • 支持表达式和函数调用
  • 性能比format()和%都快

实战对比:让你看到差距

假设你要生成一个用户信息报告:

user = {
    "id": 1001,
    "name": "张三丰",
    "email": "zhangsanfeng@wudang.com",
    "balance": 8888.88,
    "level": "VIP",
    "register_date": "2024-01-15"
}

# ❌ +号写法(代码地狱)
report = "用户ID:" + str(user["id"]) + "\n"
report += "姓名:" + user["name"] + "\n"
report += "邮箱:" + user["email"] + "\n"
report += "余额:" + str(user["balance"]) + "元\n"
report += "等级:" + user["level"] + "\n"
report += "注册时间:" + user["register_date"] + "\n"

# ❌ %格式化(参数地狱)
report = "用户ID:%d\n姓名:%s\n邮箱:%s\n余额:%.2f元\n等级:%s\n注册时间:%s\n" % (
    user["id"], user["name"], user["email"], user["balance"], user["level"], user["register_date"]
)

# ❌ format()写法(还是有点啰嗦)
report = "用户ID:{id}\n姓名:{name}\n邮箱:{email}\n余额:{balance:.2f}元\n等级:{level}\n注册时间:{register_date}\n".format(
    id=user["id"],
    name=user["name"],
    email=user["email"],
    balance=user["balance"],
    level=user["level"],
    register_date=user["register_date"]
)

# ✅ f-string写法(优雅到哭)
report = f"""用户ID:{user["id"]}
姓名:{user["name"]}
邮箱:{user["email"]}
余额:{user["balance"]:.2f}元
等级:{user["level"]}
注册时间:{user["register_date"]}"""

看出差距了吗?f-string就像在聊天,其他的就像在写说明书。

高级骚操作:f-string的隐藏技能

1. 数字格式化

price = 1234.5678

# 保留两位小数
print(f"价格:{price:.2f}")  # 价格:1234.57

# 千分位分隔符
print(f"价格:{price:,}")  # 价格:1,234.5678

# 百分比
percentage = 0.2567
print(f"完成率:{percentage:.1%}")  # 完成率:25.7%

# 对齐
name = "张三"
print(f"姓名:{name:>10}")  # 右对齐,占10位
print(f"姓名:{name:<10}")  # 左对齐,占10位
print(f"姓名:{name:^10}")  # 居中,占10位

2. 日期时间格式化

from datetime import datetime

now = datetime.now()
print(f"当前时间:{now:%Y-%m-%d %H:%M:%S}")
print(f"今天星期:{now:%A}")
print(f"今年第几天:{now:%j}")

3. 调试模式(Python 3.8+)

x = 10
y = 20

# 普通写法
print(f"x={x}, y={y}, sum={x+y}")

# 调试模式写法(f-string的f前面再加个=)
print(f"{x=}, {y=}, {x+y=}")
# 输出:x=10, y=20, x+y=30

4. 嵌套f-string(骚到飞起)

name = "孙悟空"
weapon = "金箍棒"

# 动态生成格式字符串
template = f"我叫{name},武器是{weapon}"
print(template)

# 甚至可以嵌套
padding = 10
name = "猪八戒"
print(f"{'#' * padding}{name}{'#' * padding}")
# ##########猪八戒##########

性能对比:用数据说话

让我用一个简单的性能测试:

import timeit

name = "测试用户"
age = 25

# +号拼接
def plus_concat():
    return "姓名:" + name + ",年龄:" + str(age)

# %格式化
def percent_format():
    return "姓名:%s,年龄:%d" % (name, age)

# format()方法
def str_format():
    return "姓名:{},年龄:{}".format(name, age)

# f-string
def f_string():
    return f"姓名:{name},年龄:{age}"

# 测试性能
print("性能测试(执行100万次):")
print(f"+号拼接:{timeit.timeit(plus_concat, number=1000000):.4f}秒")
print(f"%格式化:{timeit.timeit(percent_format, number=1000000):.4f}秒")
print(f"format():{timeit.timeit(str_format, number=1000000):.4f}秒")
print(f"f-string:{timeit.timeit(f_string, number=1000000):.4f}秒")

测试结果(大概):

  • +号拼接:0.8秒
  • %格式化:0.6秒
  • format():0.5秒
  • f-string:0.3秒

f-string不仅代码简洁,性能也是最快的!

什么时候不用f-string?

虽然f-string很香,但也不是万能的:

1. Python版本限制

f-string是Python 3.6+才有的,如果你的项目还在用Python 2.7或者3.5,那就老老实实用format()吧。

2. 模板系统

如果你在做复杂的模板系统(比如邮件模板、报告模板),建议用专门的模板引擎如Jinja2,而不是硬编码f-string。

3. 国际化

做多语言支持时,用专门的国际化库比f-string更合适。

真实项目中的最佳实践

日志记录

import logging

# ❌ 这样记录日志,性能差
logging.debug("用户" + username + "执行了" + action + "操作")

# ✅ 好一点
logging.debug("用户%s执行了%s操作", username, action)

# ✅ 更好(Python 3.8+)
logging.debug(f"用户{username}执行了{action}操作")

SQL查询

# ❌ 永远不要这样(SQL注入风险!)
sql = f"SELECT * FROM users WHERE name = '{user_input}'"

# ✅ 使用参数化查询
sql = "SELECT * FROM users WHERE name = %s"
cursor.execute(sql, (user_input,))

配置文件生成

config = {
    "host": "localhost",
    "port": 8080,
    "debug": True
}

# 生成配置文件
config_content = f"""# 服务器配置
host = {config["host"]}
port = {config["port"]}
debug = {config["debug"]}"""

with open("config.ini", "w", encoding="utf-8") as f:
    f.write(config_content)

总结

记住一句话:代码是写给人看的,顺便给机器执行。

f-string的优雅不在于性能多快,而在于它最接近人类的思维方式。

下次再写字符串拼接,直接上f-string,别犹豫了!


你在项目中是怎么处理字符串拼接的?

评论区聊聊,看看有没有更骚的操作。

(如果还在用+号的同学,别害羞,承认吧,我们都经历过那个阶段 😄)