当前位置: 首页 > news >正文

基于规则的数据处理框架Preswald:声明式特征工程与数据转换实践

1. 项目概述与核心价值

最近在折腾一个数据驱动的项目,需要把一堆杂乱无章的日志、用户行为数据,甚至是半结构化的JSON文件,整合成一个清晰、可查询、能直接喂给下游分析或机器学习模型的数据集。这听起来像是数据工程师的活儿,但作为一个全栈或者偏后端的开发者,我总希望能有一个更轻量、更“开发者友好”的工具,能让我在代码里像操作对象一样处理这些数据,而不是动不动就上Spark或者写一堆复杂的ETL脚本。就在这个当口,我发现了StructuredLabs/preswald这个项目。

preswald这个名字听起来有点神秘,但它的定位非常明确:一个用于数据转换和特征工程的Python库,核心是提供一个声明式的、基于规则的数据处理框架。简单来说,它让你用一套清晰、可组合的“规则”来定义数据应该如何被清洗、转换和增强,而不是写一堆过程式的、难以维护的for循环和if-else语句。这让我想起了SQL的声明式特性,但preswald更专注于单条记录(或小批量数据)的复杂转换逻辑,尤其适合特征工程这种对数据形态要求极高的场景。

它的核心价值在于“结构化”“可复用”。在数据科学和机器学习项目中,特征工程代码往往是项目里最“脏”、最难以维护的部分。不同的特征可能需要不同的预处理逻辑,这些逻辑又常常交织在一起,一旦数据源稍有变动,或者需要增加新特征,整个代码就可能需要大动干戈。preswald试图通过将转换逻辑模块化、规则化,来解决这个问题。你可以把每条转换规则看作一个独立的、可测试的组件,然后像搭积木一样把它们组合起来,形成一个完整的数据处理管道。这不仅让代码更清晰,也极大地提升了可维护性和团队协作的效率。

2. 核心设计理念与架构拆解

2.1 声明式 vs. 命令式:思维模式的转变

要理解preswald,首先要理解声明式编程在数据处理中的优势。传统的命令式数据处理,就像是在给计算机下详细的指令:“先打开这个文件,然后读一行,检查第三个字段是不是大于10,如果是就把它转换成浮点数,再乘以2,最后存到那个列表里……” 代码冗长,且业务逻辑(什么条件下做什么转换)和实现细节(如何循环、如何存储)高度耦合。

preswald采用的声明式方法,则是描述你想要的数据最终形态和转换规则:“对于字段price,我需要它是一个浮点数,并且如果原始值是字符串,需要移除货币符号;对于字段category,我需要将所有的'Electronics''Tech'映射为'E'。” 你只需要定义这些规则,preswald的引擎会负责如何高效地执行它们。这种思维模式的转变,让代码的意图变得无比清晰,阅读代码的人能立刻明白“数据要变成什么样”,而不是陷入“代码是怎么一步步做到的”细节中。

2.2 核心抽象:规则(Rule)、转换器(Transformer)与管道(Pipeline)

preswald的架构围绕几个核心抽象构建,理解它们就掌握了这个库的命脉。

规则(Rule)是最基础的单元,它定义了一个布尔条件。例如,“字段age大于 0”、“字段email包含'@'符号”。规则本身不执行转换,只用于判断。

转换器(Transformer)是执行实际数据转换的组件。一个转换器通常会与一个或多个规则关联。只有当关联的规则条件满足时,转换器才会对数据执行操作。转换器可以做很多事情:类型转换(字符串转整数)、值映射(将分类值编码为数字)、字段衍生(基于已有字段计算新字段)、缺失值填充等。

管道(Pipeline)是规则和转换器的有序集合。你可以把多个转换器(每个都可能附带自己的规则)放入一个管道中。当数据流过管道时,每个转换器会按顺序检查自己的规则是否被触发,并执行相应的转换。管道是最终组织所有数据处理逻辑的容器。

这种设计带来了巨大的灵活性。例如,你可以轻松地实现条件逻辑:如果(规则A满足)则(执行转换器X),否则如果(规则B满足)则(执行转换器Y)。你也可以将常用的转换逻辑(如“清洗美国电话号码格式”)封装成一个独立的转换器,然后在多个项目的管道中复用,真正做到“一次编写,到处运行”。

2.3 执行引擎与优化策略

preswald底层有一个执行引擎,它负责解析你定义的管道,并高效地应用于数据。虽然项目文档可能不会深入底层,但作为一个使用者,了解其可能的执行策略有助于写出更高效的规则。

  1. 惰性求值与短路逻辑:一个好的规则引擎会采用惰性求值。对于一条记录,当它流经管道时,引擎会依次评估每个转换器前的规则。如果某个转换器的规则很复杂或成本高昂,但前面的某个规则已经决定了这条记录后续的转换路径,引擎可能会跳过不必要的评估。在设计规则时,将最可能过滤掉记录的、或计算最简单的规则放在前面,可以提升整体性能。

  2. 向量化优化的可能性:虽然preswald的核心抽象是针对单条记录的,但其底层实现可能会尝试对批次数据进行向量化操作,尤其是在使用pandas DataFrame作为输入时。引擎可能会将相似的规则编译成更底层的数组操作,以减少Python循环的开销。这意味着,即使你用的是声明式接口,也可能获得接近手写优化代码的性能。

  3. 规则编译:高级的规则引擎可能会将你定义的规则“编译”成一种中间表示或直接编译成字节码,以避免每次执行时都重新解析规则定义。这尤其适用于规则固定、需要处理海量数据的场景。

3. 从入门到精通:核心功能实操详解

3.1 环境搭建与基础概念上手

首先,安装preswald通常很简单:

pip install preswald

现在,我们通过一个具体的例子来感受一下。假设我们有一组用户数据,每个用户是一个字典。

import preswald as pw # 1. 定义规则:判断用户是否成年 is_adult = pw.Rule("age") >= 18 # 2. 定义转换器:将成年用户的“会员级别”从字符串升级为“高级” upgrade_member = pw.Transformer( rule=is_adult, # 关联的规则 action=lambda data: "高级" if data.get("member_level") == "普通" else data.get("member_level"), field="member_level" # 要操作的字段 ) # 3. 创建一条数据 user_data = {"name": "张三", "age": 25, "member_level": "普通"} # 4. 应用转换器 result = upgrade_member.transform(user_data) print(result) # 输出:{'name': '张三', 'age': 25, 'member_level': '高级'}

这个简单的例子展示了核心工作流:定义规则 -> 创建关联规则的转换器 -> 应用转换Transformeraction参数是一个函数,它接收当前数据字典,并返回转换后的字段值。field参数指定了要修改或创建的字段名。

注意action函数中的data参数是当前正在处理的整条数据记录。确保你的函数能优雅地处理字段可能缺失的情况,例如使用data.get(field_name, default_value)而不是data[field_name]

3.2 构建复杂的数据处理管道

单个转换器能力有限,真正的威力在于管道。我们来构建一个处理电商订单数据的复杂例子。

import pandas as pd import preswald as pw # 模拟一些脏数据 orders = pd.DataFrame([ {"order_id": 1, "customer_id": "C001", "amount": "$150.50", "status": "pending", "items": 3}, {"order_id": 2, "customer_id": "C002", "amount": "Invalid", "status": "shipped", "items": 1}, {"order_id": 3, "customer_id": None, "amount": "89.99", "status": "delivered", "items": 5}, {"order_id": 4, "customer_id": "C004", "amount": "200", "status": "cancelled", "items": 2}, ]) # 定义一系列规则 rule_amount_valid = pw.Rule("amount").apply(lambda x: isinstance(x, str) and x.replace('.', '', 1).isdigit() or isinstance(x, (int, float))) rule_has_customer = pw.Rule("customer_id").not_null() rule_is_shipped_or_delivered = pw.Rule("status").isin(["shipped", "delivered"]) rule_high_value = pw.Rule("amount").apply(lambda x: float(x) if isinstance(x, str) else x) > 100.0 # 定义一系列转换器 # 1. 清洗金额:将字符串金额转换为浮点数,无效值设为NaN clean_amount = pw.Transformer( rule=rule_amount_valid, action=lambda data: float(data["amount"].replace('$', '')) if isinstance(data["amount"], str) else float(data["amount"]), field="amount" ) fill_invalid_amount = pw.Transformer( rule=rule_amount_valid.negate(), # 规则取反,即金额无效 action=lambda data: None, field="amount" ) # 2. 填充缺失的客户ID fill_missing_customer = pw.Transformer( rule=rule_has_customer.negate(), action=lambda data: "ANONYMOUS", field="customer_id" ) # 3. 衍生新特征:订单类别(基于金额和状态) def categorize_order(data): amount = data.get("amount") status = data.get("status") items = data.get("items", 0) if amount is None: return "invalid" elif amount > 100 and status in ["shipped", "delivered"]: return "high_value_fulfilled" elif items >= 5: return "bulk_order" else: return "standard" create_order_category = pw.Transformer( rule=pw.Rule.always_true(), # 总是执行,无需条件 action=categorize_order, field="order_category" ) # 4. 标记可疑订单(无客户ID且金额高) flag_suspicious = pw.Transformer( rule=rule_has_customer.negate() & rule_high_value, action=lambda data: True, field="is_suspicious" ) # 构建管道 # 管道的顺序很重要!通常先清洗,再转换,最后衍生特征。 pipeline = pw.Pipeline( clean_amount, fill_invalid_amount, fill_missing_customer, create_order_category, flag_suspicious ) # 应用管道到整个DataFrame # preswald通常支持对DataFrame的每一行应用管道 processed_orders = orders.copy() processed_orders = processed_orders.apply(lambda row: pipeline.transform(row.to_dict()), axis=1, result_type='expand') processed_orders = pd.DataFrame(processed_orders.tolist()) # 将结果转换回DataFrame print(processed_orders)

这个例子演示了一个相对完整的流程:

  1. 数据清洗:处理畸形的金额字符串,填充缺失的客户ID。
  2. 数据验证:通过规则隐式地验证了数据有效性(如rule_amount_valid)。
  3. 特征工程:基于多个原有字段(amount,status,items)创建了一个新的分类特征order_category
  4. 业务规则实施:定义了“可疑订单”的业务规则(无客户ID且金额高),并创建了标志字段。

实操心得:在构建管道时,顺序至关重要。一般来说,应该遵循“先清洗,后转换,再衍生”的原则。确保依赖的字段在其被使用之前已经完成了必要的清洗和准备。例如,必须在amount被清洗为浮点数之后,才能基于它创建rule_high_value规则或进行数值比较。

3.3 高级特性:自定义规则、组合规则与管道复用

自定义规则函数Rule.apply()方法非常强大,它允许你传入任何返回布尔值的函数,实现最复杂的逻辑。

def complex_business_rule(data): # 假设一个复杂的业务规则:周末下单、金额超过50、且不是老客户 from datetime import datetime is_weekend = data.get("order_date").weekday() >= 5 if data.get("order_date") else False is_high_amount = data.get("amount", 0) > 50 is_new_customer = data.get("customer_tenure_days", 365) < 30 return is_weekend and is_high_amount and is_new_customer custom_rule = pw.Rule().apply(complex_business_rule) # 注意:此规则不绑定特定字段,它检查整条记录。

规则的逻辑组合preswald支持使用&(与),|(或),~.negate()(非) 来组合规则,这让逻辑表达非常直观。

# 规则:订单有效(状态不是取消)且(金额大于100或商品数大于5) valid_order_rule = (pw.Rule("status") != "cancelled") & ( (pw.Rule("amount") > 100) | (pw.Rule("items") > 5) ) # 规则:客户ID为空或邮箱格式无效 from email.utils import parseaddr def is_valid_email(email): return '@' in parseaddr(email)[1] bad_customer_rule = pw.Rule("customer_id").is_null() | ~pw.Rule("email").apply(is_valid_email)

管道的复用与组合:你可以将常用的小管道封装成函数或类,然后在更大的管道中作为组件使用。这是实现代码模块化的关键。

def create_data_cleaning_pipeline(): """返回一个专门用于数据清洗的管道""" return pw.Pipeline( pw.Transformer(rule=pw.Rule("price").not_null(), action=clean_price, field="price"), pw.Transformer(rule=pw.Rule("category").not_null(), action=standardize_category, field="category"), # ... 更多清洗规则 ) def create_feature_engineering_pipeline(): """返回一个专门用于特征工程的管道""" return pw.Pipeline( pw.Transformer(rule=always_true, action=create_time_features, field=["hour_of_day", "day_of_week"]), pw.Transformer(rule=always_true, action=aggregate_user_history, field="user_avg_spend"), # ... 更多特征衍生规则 ) # 主管道组合了清洗和特征工程 main_pipeline = pw.Pipeline( create_data_cleaning_pipeline(), create_feature_engineering_pipeline() )

4. 性能调优、调试与最佳实践

4.1 性能考量与优化技巧

虽然声明式编程提升了开发效率,但在处理大规模数据时,性能仍需关注。

  1. 规则复杂度:在Rule.apply()中使用的自定义函数要尽可能高效。避免在其中进行耗时的I/O操作(如数据库查询、网络请求)或复杂的计算。如果必须进行,考虑能否将结果预先计算好作为字段加入数据。

  2. 规则顺序:如前所述,将过滤性最强(能排除最多记录)或计算最简单的规则放在管道前面。这可以利用短路求值,避免对不满足条件的记录执行后续昂贵的规则评估和转换。

  3. 批处理与向量化:如果底层数据是pandas DataFrame,确保你的action函数能够很好地与pandas.Series操作兼容。有时,对于简单的列级操作,直接使用pandas的向量化方法(如.str.replace(),.astype(),.map())可能比通过preswald逐行处理更高效。preswald更适合处理行间有复杂依赖、条件逻辑交织的场景。评估你的使用场景,必要时可以混合使用。

  4. 缓存不变规则:如果某些规则或转换的结果在多次运行中是不变的(例如,基于静态映射表的编码),考虑在管道外部预先计算好映射字典,然后在action函数中直接查表,而不是每次执行都重新计算映射。

4.2 调试与测试策略

声明式管道的调试可能比命令式代码更抽象,以下是几个有效策略:

  1. 单元测试每个转换器:这是最重要的实践。为每个Transformer编写独立的单元测试,提供各种边界情况的输入数据,验证其输出是否符合预期。由于转换器是纯函数(给定相同输入,产生相同输出),它们非常易于测试。

    def test_clean_amount_transformer(): transformer = clean_amount # 引用之前定义的转换器 # 测试正常情况 assert transformer.transform({"amount": "$150.50"})["amount"] == 150.5 # 测试无效情况(应被其他转换器处理,此转换器不触发) # 需要测试规则是否被正确触发 assert clean_amount.rule.check({"amount": "Invalid"}) == False
  2. 可视化管道执行:可以编写一个简单的调试函数,打印出数据流经管道时每个步骤的状态。

    def debug_pipeline(pipeline, data): print(f"原始数据: {data}") for i, transformer in enumerate(pipeline.transformers): rule_result = transformer.rule.check(data) if transformer.rule else True print(f"步骤 {i} [{transformer.field}]: 规则触发={rule_result}") if rule_result: old_val = data.get(transformer.field) data = transformer.transform(data) new_val = data.get(transformer.field) print(f" 值变化: {old_val} -> {new_val}") else: print(f" 跳过") print(f"最终数据: {data}") return data
  3. 使用样本数据:在开发管道时,准备一小套具有代表性的样本数据(包含各种正常和异常情况),并手动验证管道在样本上的输出。这能快速发现逻辑错误。

4.3 项目集成与部署实践

  1. 配置化:对于需要频繁调整阈值或映射关系的规则(如“高金额”的阈值、分类映射表),不要将这些值硬编码在规则定义里。应该从配置文件(如YAML、JSON)或环境变量中读取,然后在创建规则时注入。这使得业务规则可以在不修改代码的情况下进行调整。

    import yaml with open('rules_config.yaml', 'r') as f: config = yaml.safe_load(f) high_value_threshold = config['rules']['high_value_threshold'] category_mapping = config['mappings']['product_category'] rule_high_value = pw.Rule("amount") > high_value_threshold
  2. 版本控制管道:将管道定义代码纳入版本控制(如Git)。当特征工程逻辑发生变化时,你可以清晰地追溯修改历史,并且能够回滚到之前可用的版本。这对于模型的可复现性至关重要。

  3. 与机器学习工作流集成preswald管道可以完美地集成到scikit-learnPipeline中,作为一个自定义的TransformerMixin。这样,你的整个数据处理和特征工程流程就可以与模型训练、交叉验证、网格搜索等步骤无缝结合,并享受sklearn的序列化(pickle)支持。

    from sklearn.base import BaseEstimator, TransformerMixin class PreswaldTransformer(BaseEstimator, TransformerMixin): def __init__(self, pipeline): self.pipeline = pipeline def fit(self, X, y=None): # preswald管道通常是无状态的,fit方法可空实现 return self def transform(self, X): # 假设X是pandas DataFrame return X.apply(lambda row: self.pipeline.transform(row.to_dict()), axis=1) # 在sklearn管道中使用 from sklearn.pipeline import Pipeline as SklearnPipeline from sklearn.ensemble import RandomForestClassifier ml_pipeline = SklearnPipeline([ ('feature_engineering', PreswaldTransformer(my_preswald_pipeline)), ('classifier', RandomForestClassifier()) ])

5. 常见问题、陷阱与解决方案实录

在实际使用preswald或类似声明式框架的过程中,我踩过不少坑,也总结出一些共性的问题和解决方法。

问题1:规则执行顺序不符合预期,或者转换结果互相覆盖。

  • 根因:管道中转换器的执行顺序是定义时的顺序。如果转换器A基于字段X的原始值生成了字段Y,而转换器B又修改了字段X,那么转换器A的计算基础就变了。或者,两个转换器试图修改同一个字段,后者会覆盖前者。
  • 解决方案
    • 画数据流图:在纸上或白板上画出每个转换器的输入和输出字段,理清依赖关系。确保被依赖的字段先被处理。
    • 遵循“先读后写”原则:如果一个转换器需要读取多个字段来计算新值,确保这些源字段在它之前没有被意外修改。如果必须修改源字段,考虑是否先复制一份到临时字段。
    • 使用字段前缀:对于中间计算字段,可以使用_temp__derived_作为前缀,避免与最终输出字段混淆,并在管道末尾选择性地删除它们。

问题2:处理大型数据集时速度非常慢。

  • 根因:逐行应用Python函数(action中的lambda或自定义函数)本身就有开销。如果规则复杂或数据量极大(数百万行),性能瓶颈会非常明显。
  • 解决方案
    • 审视规则:检查是否有规则可以简化或合并。过于复杂的自定义函数是主要瓶颈。
    • 批处理与向量化:如果可能,将数据转换为pandas DataFrame,并看看能否将一部分转换逻辑用pandasnumpy的向量化操作重写。preswald管道可以和向量化操作结合使用,前者处理复杂条件逻辑,后者处理简单列变换。
    • 并行化:对于可以独立处理的行,考虑使用multiprocessingjoblib进行并行处理。但要注意数据拆分和合并的开销。
    • 采样开发,全量验证:在开发调试阶段,使用数据样本(如1%)。只有在逻辑完全正确后,再应用到全量数据。

问题3:规则逻辑正确,但某些记录没有被正确转换。

  • 根因:最常见的原因是数据本身的多样性与边界情况超出预期,或者规则的条件判断存在漏洞(例如,对None、空字符串、数字0、布尔值False的判断混淆)。
  • 解决方案
    • 增强数据探查:在构建规则前,对数据进行彻底的统计分析,了解每个字段的取值分布、缺失率、异常值。
    • 编写防御性规则:使用Rule.apply()时,函数内部要做好异常捕获和默认值处理。
    • 添加默认转换器:在管道末尾,可以添加一个“兜底”转换器,其规则是“前面所有规则都不满足”,用于处理未预见的情况,例如记录一条警告日志或将字段设为特定的默认值(如“UNKNOWN”)。
    • 启用详细日志:如前所述,使用调试函数或为管道添加日志记录功能,跟踪每条记录经过每个转换器的状态。

问题4:管道代码变得很长,难以维护。

  • 根因:将所有规则和转换器都堆砌在一个地方。
  • 解决方案
    • 模块化:按功能域拆分管道。例如,创建cleaning.pyenrichment.pyfeature_derivation.py,每个文件导出自己的子管道。
    • 使用配置驱动:将业务规则(阈值、映射表、开关)提取到配置文件中。管道代码只负责组装逻辑骨架。
    • 创建领域特定语言(DSL):如果业务非常复杂,可以考虑在preswald之上封装一层更贴近业务语言的DSL。例如,定义一个BusinessRule类,将“高价值用户”、“活跃会话”等业务概念转化为底层的preswald规则组合。

问题5:如何测试整个管道的集成效果?

  • 解决方案:创建“黄金数据集”测试。
    1. 准备一份精心构造的输入测试数据,覆盖所有重要的业务场景和边界情况。
    2. 手动(或通过可信的独立脚本)计算出这批数据经过理想处理后的预期输出结果,形成“黄金标准”数据集。
    3. 在CI/CD流程中,添加一个测试用例,用你的preswald管道处理输入数据,并将结果与“黄金标准”数据集进行对比(可以使用pandas.testing.assert_frame_equal等工具)。
    4. 任何对管道逻辑的修改,都必须通过这个集成测试,确保不会破坏已有的数据处理行为。这是保证数据质量 pipeline 可靠性的基石。

preswald这类工具的价值,在于它引入了一种更清晰、更可维护的数据处理范式。它可能不会在所有场景下都比手写代码更快,但在逻辑复杂性、团队协作和长期维护成本上,它能带来显著的收益。对于特征工程、数据清洗规则多变的中大型数据项目,花时间学习和引入这样的框架,长远来看绝对是值得的。最关键的是,它迫使你更结构化地思考数据转换逻辑,这本身就是一个好习惯。

http://www.jsqmd.com/news/748505/

相关文章:

  • 从MySQL 5.7升级到8.1,我踩过的那些坑:MSI安装、环境变量与Navicat连接2059错误全解决
  • 2026成都气泡膜技术解析:珍珠棉酒托、电商专用气泡膜、电商快递气泡袋、四川气泡膜复合珍珠棉、四川珍珠棉、异形珍珠棉选择指南 - 优质品牌商家
  • YOLOv9涨点新思路:手把手教你用DySample替换上采样层(附训练配置文件详解)
  • 2026.02 飞书 V7.62 更新了哪些内容?多维表格默认布局一键恢复,仪表盘切片器支持文本搜索
  • 无我之刃,如何斩向“后世的实体”——论佛学对现代性“法执”的未预见
  • iTerm2隐藏玩法大揭秘:从窗口快照到按键回放,打造你的专属终端工作台
  • 视觉语言模型优化:视觉提示与网格分辨率实践指南
  • Python医疗影像调试最后的“黑箱”:NIfTI头文件校验、BIDS格式合规性、JSON侧车文件同步——这3个被99%开发者忽略的元数据断点
  • Android - Bitmap
  • 从模型到部署:手把手教你用Sophon SAIL在BM1684X上跑通第一个Python推理Demo
  • 别再瞎调YOLOv5的imgsz了!从640到1280,实测不同尺寸对训练速度和精度的真实影响
  • 保姆级教程:用PyTorch从零实现MAPPO算法(附完整代码与避坑指南)
  • HiFloat4:优化语言模型推理的4位块浮点格式
  • 大语言模型工程实战:从评估、结构化输出到安全部署的避坑指南
  • 手把手调参:基于海思PID源码,实战调试PMSM FOC双环(电流环+速度环)
  • 量子加密克隆技术:突破不可克隆定理的新方法
  • SSL剥离攻击入门:sslstrip工具快速上手指南
  • Sunshine游戏串流终极指南:三步搭建你的跨平台游戏服务器
  • 初创公司如何利用 Taotoken 低成本试错多种大模型
  • 飞书 V7.63 更新了哪些内容?AI 粘贴、AI 语音录入、AHA 电脑医生一次讲清楚
  • 2026电气防爆检测全指南:四川防爆检测公司/四川防雷检测公司/工厂防雷检测/工地防雷检测/成都防爆检测公司/成都防雷检测公司/选择指南 - 优质品牌商家
  • ZooKeeper C++客户端避坑指南:从`zookeeper_mt`多线程模型到临时节点心跳丢失的实战解析
  • Bits UI高级技巧:10个提升开发效率的实用方法
  • 可微分LUT技术:硬件友好型神经网络实现
  • Windows 10/11 上保姆级安装Nessus 10.7.1,附离线激活与插件加载避坑指南
  • 告别盲人摸象:用QEMU + GDB单步调试,可视化学习NVMe寄存器读写全过程
  • 从Moment.js中文配置,聊聊前端国际化(i18n)的那些“坑”:以日期时间处理为例
  • 2026/03/30飞书 V7.65 功能更新详解:AI 深度融合办公场景,aily、妙搭、多维表格与妙记全面升级
  • vim-one 在 tmux 和 Neovim 中的高级配置指南
  • 别再只用Matplotlib了!用PyEcharts在VSCode里5分钟搞定动态交互图表(附完整代码)