You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为动态导入生成的子类与方法提供LSP自动补全支持

如何为动态导入生成的子类与方法提供LSP自动补全支持

我完全懂你的痛点——动态生成的子类和方法功能跑起来没问题,但LSP根本识别不了,手动写.pyi stub文件不仅费时间,后续代码更新还得同步修改,太折腾人了。下面给你几个更高效的解决方案,你可以根据自己的项目场景选:

方案一:自动生成Stub文件

既然手动写Stub麻烦,那我们就写个脚本自动生成。思路是在项目启动、构建,或者每次更新代码后,自动遍历子目录和类文件,提取类的签名、文档字符串,输出成符合规范的Stub文件。

举个简单的实现脚本示例:

import ast
from pathlib import Path

def generate_validation_stubs(parent_class_name: str, root_dir: Path, output_path: Path):
    # 初始化Stub文件内容
    stub_content = f"from typing import Callable, TypeVar\nValidate = TypeVar('Validate')\n\nclass {parent_class_name}:\n"

    # 遍历所有子目录
    for subdir in [d for d in root_dir.iterdir() if d.is_dir()]:
        subclass_name = subdir.name
        stub_content += f"    class {subclass_name}:\n"

        # 遍历子目录下的Python文件(排除__init__.py)
        for py_file in [f for f in subdir.glob("*.py") if f.name != "__init__.py"]:
            with open(py_file, "r", encoding="utf-8") as f:
                tree = ast.parse(f.read())
            
            # 提取文件中的类定义
            for node in ast.walk(tree):
                if isinstance(node, ast.ClassDef):
                    # 提取类的文档字符串
                    docstring = ast.get_docstring(node) or ""
                    # 提取__init__方法的参数(简化处理,适配大多数场景)
                    init_method = next((n for n in node.body if isinstance(n, ast.FunctionDef) and n.name == "__init__"), None)
                    if init_method:
                        # 整理参数列表,保留self和用户定义的参数
                        args = ["self"] + [arg.arg for arg in init_method.args.args if arg.arg != "self"]
                        args_str = ", ".join(args)
                        method_sig = f"        def {node.name}({args_str}) -> Validate: ...\n"
                    else:
                        method_sig = f"        def {node.name}(self) -> Validate: ...\n"
                    
                    # 写入Stub内容
                    stub_content += method_sig
                    if docstring:
                        stub_content += f'        """{docstring}"""\n'
    
    # 写入Stub文件
    with open(output_path, "w", encoding="utf-8") as f:
        f.write(stub_content)

# 使用示例:假设你的验证类都在validations目录下
generate_validation_stubs(
    parent_class_name="MyClass",
    root_dir=Path(__file__).parent / "validations",
    output_path=Path(__file__).parent / "my_class.pyi"
)

你可以把这个脚本集成到项目的pre-commit钩子、启动脚本里,或者每次新增/修改验证类后手动运行一次,就能自动同步Stub文件,完全不用手动复制粘贴。

方案二:给父类添加__dir__方法提升补全体验

如果不想折腾Stub生成,最快的方式是给父类添加__dir__方法,返回所有动态生成的子类和方法名。这样LSP至少能列出这些可选的名字,虽然没法提供完整的参数提示,但能解决“看不到可用选项”的核心问题。

示例代码:

from pathlib import Path

class MyClass:
    def __dir__(self):
        # 获取父类默认的可访问属性列表
        original_dir = super().__dir__()
        validation_root = Path(__file__).parent

        # 添加所有子目录名(对应动态生成的子类)
        subdir_names = [d.name for d in validation_root.iterdir() if d.is_dir()]
        original_dir.extend(subdir_names)

        # 添加每个子类下的方法名(对应动态导入的类)
        for subdir in validation_root.iterdir():
            if not subdir.is_dir():
                continue
            for py_file in subdir.glob("*.py"):
                if py_file.name == "__init__.py":
                    continue
                # 这里和你原代码的类名转换逻辑保持一致
                class_name = py_file.stem.replace("_", "").capitalize()
                original_dir.append(class_name)
        
        return original_dir

方案三:改用静态类结构+装饰器注册(最完善的LSP支持)

如果可以稍微调整现有代码结构,放弃完全动态的导入逻辑,改用装饰器注册的方式,既能保留动态扩展的灵活性,又能让LSP识别完整的静态类型。

步骤如下:

  1. 先在父类里定义好静态的子类框架:
class MyClass:
    # 提前定义好对应子目录的子类框架
    class subdirectory1:
        pass
    
    class subdirectory2:
        pass

    def __create_validation_class__(self, cls, *args, **kwargs):
        # 你的原逻辑,创建验证实例
        ...
  1. 写一个装饰器,用来把各个模块的类注册到对应子类下:
def register_validation(subclass_name: str):
    def decorator(cls):
        # 获取父类中对应的子类
        target_subclass = MyClass.__dict__[subclass_name]
        # 把类转换成可调用的方法,绑定到子类上
        def validation_method(self, *args, **kwargs):
            return self.__create_validation_class__(cls, *args, **kwargs)
        # 保留原类的名字和文档字符串
        validation_method.__name__ = cls.__name__
        validation_method.__doc__ = cls.__doc__
        setattr(target_subclass, cls.__name__, validation_method)
        return cls
    return decorator
  1. 在子目录的Python文件中使用装饰器注册类:
# subdirectory1/child_class.py
from my_class import register_validation

@register_validation("subdirectory1")
class ChildClass:
    """这是一个验证类的文档字符串"""
    def __init__(self, param: str):
        self.param = param

这种方式下,MyClass有静态定义的子类结构,LSP能直接识别所有子类和方法,甚至能提供参数提示、文档字符串预览,是体验最好的方案。


备注:内容来源于stack exchange,提问作者Akmal Soliev

火山引擎 最新活动