
甩开代码嵌套, 摆脱代码混乱, 10个“小而美妙”的技巧, 让你的工作流水线全面改观, 面貌截然不同!
我于领域方面有着长时间深入钻研, 在引领团队这件事上, 在交付工具这件事上, 于对陈旧代码加以重构这件事上, 我察觉到一种规律, 那即真正意义深刻令人瞩目的相当大的进步常常是源自细微的调整事项, 也许仅仅只是一行代码的变更, 一个全新习惯的养成状况, 或者一个你不曾听闻过的库的运用情形。
以下是我于日常工作当中所发现且加以应用的10个, 那种“小而美”的技巧, 它们把我的工作流程给彻底重塑了, 我坚信, 把它们融入到你的日常编程里面, 同样能够让你的“每一天”运行得更为顺畅、高效。
一、告别多层嵌套函数调用, 构建清晰的数据转换链, 使用toolz.pipe, 代码读起来就如同读英文那般流畅, 标点成句清晰表达, 句末加标点符号。
当处理数据之际, 我们时常得针对原始数据开展一连串的转换、清洗甚至于处理, 传统所用的办法乃是多层函数嵌套调用, 如此一来会致使代码的可读性大幅降低, 尤其是在转换步骤有所增多之时。
解决方案: 使用外部库toolz中的pipe函数。
pipe函数所具备的作用, 是把数据依照次序传递给一系列的函数, 进而展开处理, 而这个处理所产生的结果, 则是能够即刻提升代码的可读性, 你的具备这般特性的数据处理流水线, 将会如同阅读英文句子那般自然。
假定我们要针对一个存有空格以及空字符串的列表予以清洗, 并且最终将其转变为整数列表。
from toolz import pipe
def clean(data):
# 移除每个字符串两端的空白
return [x.strip() for x in data]
def filter_out(data):
# 过滤掉空字符串
return [x for x in data if x]
def to_int(data):
# 将所有元素转换为整数
return list(map(int, data))
raw = [" 1 ", "", " 2 ", "3"]
# 使用pipe函数,数据按顺序依次经过clean, filter_out, to_int处理
result = pipe(raw, clean, filter_out, to_int)
print(result) # 输出: [1, 2, 3]
这个技巧虽然微小,但能让你的数据处理流程图清晰地展现出来。
二、在开发之中, 实现即时且可读的那种日志输出, 并不需要复杂进行相关配置, 这样就能使得你的那段开发日志, 马上拥有时间戳那一块儿内容, 还有级别以及彩色高亮的效果。
于开发进程当中, 日志也就是log, 它乃是定位问题以及了解程序运行状态的关键所在。然而, 好多开发者在项目较为早期的时候常常会忽视对输出格式进行优化, 一直到后期才着手去关注。
解决方案: 使用外部库。
可以使你在不用进行配置的情形下, 马上获取到有意义且可读性非常高的日志输出, 它能够自动给你的日志增添时间戳, 日志还具有的那些级别(像DEBUG、信息信息也在此为INFO来说、还有所谓的ERROR等等), 并且是以彩色格式予以展示的, 这样极其显著地提升了开发体验。
示例:
import logging, coloredlogs
logger = logging.getLogger('myapp')
# 传入日志级别和格式,即可安装彩色日志
coloredlogs.install(level='DEBUG', logger=logger,
fmt='%(asctime)s %(levelname)s %(message)s')
logger.info("Started process")
这个库是有价值的, 其价值体现于, 它能够使得你, 在开发开始的较早阶段, 就可以获取到具备较高质量的日志输出, 进而能够提前发现潜藏的问题。
三、借助.简化错误, 将忽略块运用, 以仅仅一行代码, 清晰地去表达“我知道这个操作极有可能出错, 还请忽略它”。
在你执行某一操作之际, 要是预期其或许会因某些缘由(像是文件不存在此类情况)从而抛出错误, 不过你又并不期望程序因之而中断, 传统的做法是运用try/: pass结构。
解决方案: 使用标准库中的上下文管理器。
用于起到抑制作用的是将指定的异常给予抑制。和传统的try/: pass相比较而言, 它能够以更为清晰以及更为简洁的方式把意图表达出来: 我清楚在此处有可能会出现这个错误, 要将它压制住。
示例:
from contextlib import suppress
import os
# 尝试删除一个文件,如果文件不存在,则忽略 FileNotFoundError 异常
with suppress(FileNotFoundError):
os.remove("tempfile.txt")
它将多行代码简化为一行,并且意图清晰、可读。
四、将初始化专属参数进行处理, 为对象设置“一次性”参数, 此参数仅用于构建实例, 不会被存储为对象的属性。
当运用(数据类)之际, 偶尔我们期望某个参数, 仅于对象开展初始化操作之时, 用以设定别的属性, 然而在那之后, 却不期望它作为对象的一则长久属性, 被予以存储或者展示。
解决方案: 使用.。
把参数定义成类型之后, 它会于初始化期间传入方法, 以供咱们开展设置操作, 然而它不会变成数据类实例的一个字段。这在处理敏感信息(像密码)时格外有用, 能够确保原始密码在初始化完毕后即刻被清除, 仅仅留存_HASH#值。
示例:
from dataclasses import dataclass, field, InitVar
def hash(p): return f"hashed-{p}" # 模拟哈希函数
@dataclass
class User:
name: str
# raw_password 仅用于初始化
raw_password: InitVar[str]
# hashed 属性不需要在初始化时传入,但会在 __post_init__ 中设置
hashed: str = field(init=False)
# 接收 InitVar 参数
def __post_init__(self, raw_password):
# 使用 raw_password 计算哈希值
self.hashed = hash(raw_password)
u = User("alice", "secret")
print(u) # 打印结果不会包含 raw_password 属性
# 输出: User(name='alice', hashed='hashed-secret')
这个微妙的技巧实现了代码的整洁和安全性。
五、告别共享变量轮询, 借助.Event去协调异步任务, 让异步任务间的协作凭借事件而变得更加明确。
撰写异步的代码之际, 不同的任务之间常常得开展协调以及同步。一种平常会犯的错误做法是运用共享的布尔变量也就是旗标或者说flag来做轮询检查, 或者依靠全局变量去传递状态。
解决方案: 使用.Event。
在异步编程里, Event属于一个基础同步原语, 它可让一个任务, 明确地去等待另一个任务, 将一个“事件”设置成发生的状态, 并且这样做能让任务之间的协作意图,变得更加清晰明了, 进而让代码, 变得更加优美雅致, 且具备更好的可读性。
示例:
import asyncio
done = asyncio.Event() # 创建一个事件对象
async def worker():
print("Working…")
await asyncio.sleep(1)
print("Done, signaling")
done.set() # 工作完成,设置事件
async def main():
asyncio.create_task(worker())
# main 任务会在此处暂停,直到 worker 任务调用 done.set()
await done.wait()
print("Main resumes")
asyncio.run(main())
# 输出顺序:
# Working…
# Done, signaling
# Main resumes
这是一种在异步脚本中实现同步的优雅方式。
六、依照..()去检查依赖版本, 于代码运行之际实施版本校验, 以此保证脚本处在正确依赖环境当中运行。
特定版范围外依赖库里, 当你的脚本靠它时, 将运行版本弄对很关键。依赖库版本若不对头, 无论是过老还是过新, 程序都可能出岔子。
解决方案: 使用标准库.中的()函数。
能在代码执行起始阶段对关键依赖库版本予以检查, 这样的话倘若版本不符合规定要求从而能够迅速失败, 进而防止运行到后期才察觉到版本的问题诱发难以排查的错误, 这是一项规模较小然而却能够大幅度提高代码可靠性的防护举措。
示例:
from importlib.metadata import version, PackageNotFoundError
try:
# 获取 requests 库的当前版本
v = version("requests")
except PackageNotFoundError:
# 库未安装,给出提示
print("requests not installed")
else:
# 检查版本是否低于要求 (例如 2.25.0)
if v < "2.25.0":
# 版本过旧,抛出运行时错误
raise RuntimeError(f"requests version {v} is too old")
else:
print(f"requests version {v} is ok.")
在分发工具或部署应用时,这个小技巧能提供强大的可靠性保障。
七、克隆以及修改不可变数据以清理配置方面那些用于管理 的和有关状态快照的代码 , 去达成在不可变对象上的快速更新。
处于那种情况里面, 不可以改变其状态的对象, 能够对减少程序里出现的附带作用有所帮助, 进而让状态的管理变得更为安全可靠。针对配置的管理或者状态的快速记录而言, 我们时常会有这样的需求, 那就是依据一个已经存在的对象去构造出一个稍微存在差异的全新对象。
解决方案: 使用中的函数,并配合=True的不可变数据类。
数据类在被设置为等于真的时候, 就变成不可变的了。函数能够接纳一个数据类实例以及要进行修改的字段, 随后返回一个全新的实例, 并且让原实例维持不变。这使得对于配置或者状态的“克隆并修改”这种操作变得规整简洁。
示例:
from dataclasses import dataclass, replace
# 设置 frozen=True 使 Config 实例不可变
@dataclass(frozen=True)
class Config:
host: str
port: int
cfg1 = Config("localhost", 8080)
# 基于 cfg1 创建一个新实例 cfg2,只修改 port 字段
cfg2 = replace(cfg1, port=9090)
print(cfg1) # Config(host='localhost', port=8080)
print(cfg2) # Config(host='localhost', port=9090)
# cfg1 保持不变,实现了配置的“版本”管理
是保证系统状态稳定的关键, 是减少副作用的关键, 在于不可变对象与快速修改的结合。
八、替代外部Cron/定时器, 在内部用sched.实现简单定时任务, 以使你的任务调度拥有跨平台可移植性。
针对那种在脚本内部依照周期性去运行任务的需求状况, 好多开发者常常会下意识地借助外部的系统定时工具, 就像Linux/Unix系统里的cron一样。然而这样做却致使脚本的跨平台移植性被牺牲掉了。
解决方案: 使用标准库sched中的。
sched.所支持准许你能够于纯粹的环境范围之内, 以单个文件、跨越不同平台的方式途径去达成实现那种单纯简易的调度方面的任务具体事宜。它凭借运用系统自身的time.time来获取得到时间信息, 并且利用使用time.sleep去进行暂停停顿动作, 以此来等待等候下一次任务的执行开展。它并不需要借助任何的外部相依依赖条件, 极为实用便利而且极具拥有那种可以被移植的特性属性。
示例:
import sched, time
# 初始化调度器,传入时间获取函数和休眠函数
schedr = sched.scheduler(time.time, time.sleep)
def task():
print("Running scheduled task", time.time())
# 任务执行完成后,再调度自己,10秒后再次执行
schedr.enter(10, 1, task)
# 首次调度:10秒后,优先级1,执行 task 函数
schedr.enter(10, 1, task)
# 启动调度器
schedr.run()
这是一种实用方法, 旨在不需要借助外部工具的状况下, 达成轻量级的定时任务。
九、为非密码学用途, 生成安全的随机数,提供比标准库更好的不可预测性。
当处于需要去生成一次性口令也就是Token、密钥或者别的需要高度不可预测性的随机数据的状况下, 那种标准的库常常是不够安全的。
解决方案: 使用标准库中的。
其所运用的乃是操作系统所供给的质量最为上乘的随机源, 相较于由标准库生成出的数字, 它具备更佳的随机性特质, 所以相比较而言更适用于对不可预测之性质存有需求的场景, 诸如去生成用户身份进行验证的令牌。与此同时, 它规避了引入更为重型质量的加密库这一情况。
示例:
import secrets, string
def gen_token(length=16):
# 定义可用的字符集:大小写字母和数字
alphabet = string.ascii_letters + string.digits
# 使用 secrets.choice() 从字符集中随机选取字符
return ''.join(secrets.choice(alphabet) for _ in range(length))
print(gen_token()) # 输出例如: Pz1aBcD2E3fG4H5J6K7L8
当下, 当发展任何一项, 其目的在于生成令牌或者唯一ID的中级自动化工具之际, 此样的技巧常常极易被人所忽略。
十、借助.达成延迟加载的属性, 使延迟执行重量级计算得以实现, 仅在首次进行访问之际加载数据, 以此让对象变得更为轻量, 并且让响应变得更快。
一个对象, 有可能包含一些“重量级”的属性, 这些属性需要进行大量的计算, 或者要从外部读取数据。要是这些属性并非每次都会被用到, 那么在对象开始初始化的时候就对它们进行计算, 这会造成资源的浪费, 并且还会降低对象的创建速度。
解决方案: 使用标准库中的装饰器。
将一个方法转化为一个属性是@所具备的作用, 然而, 这个方法仅仅会在第一次对该属性进行访问的时候才会去执行, 执行过后所产生的结果会被缓存起来, 在后面的访问当中将会直接返回缓存值回去, 并非再度执行十分复杂需历经长时间的计算, 如此一来就避免了你在针对对象进行设计时要手动去编写类似‘假如self.data的值为无返回真’这样的检查代码。
示例:
from functools import cached_property
class BigData:
def __init__(self, source):
self.source = source
@cached_property
def data(self):
# 这是一个重量级计算或加载过程
print("Loading heavy data…")
return list(range(1_000_000)) # 模拟加载一百万个元素
b = BigData("file.csv")
print("Before access")
# 第一次访问 data 属性,会执行 print("Loading heavy data…")
print(len(b.data))
print("Second access – no load")
# 第二次访问 data 属性,直接使用缓存结果,不会再次执行加载
print(len(b.data))
这个技巧, 能够显著地提升对象的响应快慢程度, 并且保证资源, 只会在绝对有必要的时候, 才会被消耗掉。
总结与展望
这10个技巧, 它们之中的多数都显得“微小”, 然而呢, 却能够给你的日常工作带去极大的动力, 以及带来改变。从清晰的函数管道开始说起, 到安全的随机数生成, 再到优雅的异步协作, 他们当中的每一个都是用来提升代码质量, 同时, 也是用于提高开发效率的有力工具。
把这些被称作“小赢”的内容融入, 你的编程习惯之中, 你就会发觉, 你的代码将会变得更加清晰, 更加安全, 更加高效。
