在尝试使用
libcst
模块时,我在更新函数文档时遇到一些困难。
为了重现该错误,包含以下最小工作示例 (MWE):
from libcst import ( # type: ignore[import]
Expr,
FunctionDef,
IndentedBlock,
MaybeSentinel,
SimpleStatementLine,
SimpleString,
parse_module,
)
original_content: str = """
\"\"\"Example python file with a function.\"\"\"
from typeguard import typechecked
@typechecked
def add_three(*, x: int) -> int:
\"\"\"ORIGINAL This is a new docstring core.
that consists of multiple lines. It also has an empty line inbetween.
Here is the emtpy line.\"\"\"
return x + 2
"""
new_docstring_core: str = """\"\"\"This is a new docstring core.
that consists of multiple lines. It also has an empty line inbetween.
Here is the emtpy line.\"\"\""""
def replace_docstring(
original_content: str, func_name: str, new_docstring: str
) -> str:
"""Replaces the docstring in a Python function."""
module = parse_module(original_content)
for node in module.body:
if isinstance(node, FunctionDef) and node.name.value == func_name:
print("Got function node.")
# print(f'node.body={node.body}')
if isinstance(node.body, IndentedBlock):
if isinstance(node.body.body[0], SimpleStatementLine):
simplestatementline: SimpleStatementLine = node.body.body[
0
]
print("Got SimpleStatementLine")
print(f"simplestatementline={simplestatementline}")
if isinstance(simplestatementline.body[0], Expr):
print(
f"simplestatementline.body={simplestatementline.body}"
)
simplestatementline.body = (
Expr(
value=SimpleString(
value=new_docstring,
lpar=[],
rpar=[],
),
semicolon=MaybeSentinel.DEFAULT,
),
)
replace_docstring(
original_content=original_content,
func_name="add_three",
new_docstring=new_docstring_core,
)
print("done")
运行
python mwe.py
产量:
Traceback (most recent call last):
File "/home/name/git/Hiveminds/jsonmodipy/mwe0.py", line 68, in <module>
replace_docstring(
File "/home/name/git/Hiveminds/jsonmodipy/mwe0.py", line 56, in replace_docstring
simplestatementline.body = (
^^^^^^^^^^^^^^^^^^^^^^^^
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'body'
如何使用 libcst 模块替换某些 Python 代码
add_three
中名为 file_content
的函数的文档字符串?
我找到了以下基本示例的解决方案,但是,我没有在类内的不同函数(使用类型化参数、类型化返回等)上测试它。
from pprint import pprint
import libcst as cst
import libcst.matchers as m
src = """\
import foo
from a.b import foo_method
class C:
def do_something(self, x):
\"\"\"Some first line documentation
Some second line documentation
Args:something.
\"\"\"
return foo_method(x)
"""
new_docstring:str = """\"\"\"THIS IS A NEW DOCSTRING
Some first line documentation
Some second line documentation
Args:somethingSTILLCHANGED.
\"\"\""""
class ImportFixer(cst.CSTTransformer):
def leave_SimpleStatementLine(self, orignal_node, updated_node):
"""Replace imports that match our criteria."""
if m.matches(updated_node.body[0], m.Expr()):
expr=updated_node.body[0]
if m.matches(expr.value, m.SimpleString()):
simplestring=expr.value
print(f'GOTT={simplestring}')
return updated_node.with_changes(body=[
cst.Expr(value=cst.SimpleString(value=new_docstring))
])
return updated_node
source_tree = cst.parse_module(src)
transformer = ImportFixer()
modified_tree = source_tree.visit(transformer)
print("Original:")
print(src)
print("\n\n\n\nModified:")
print(modified_tree.code)
例如,此部分解决方案失败:
src = """\
import foo
from a.b import foo_method
class C:
def do_something(self, x):
\"\"\"Some first line documentation
Some second line documentation
Args:something.
\"\"\"
return foo_method(x)
def do_another_thing(y:List[str]) -> int:
\"\"\"Bike\"\"\"
return 1
"""
因为解决方案不会验证出现
SimpleString
的函数的名称。
您为什么会收到“FrozenInstanceError”?
如您所见,
libcst
生成的 CST 是由不可变节点组成的图(每个节点代表 Python 语言的一部分)。如果要更改节点,实际上需要为其创建一个新副本。
这是使用 node.with_changes()
方法完成的。
因此您可以在第一个代码片段中执行此操作。 然而,还有更多“优雅”的方法来实现这一点,部分记录在 libcst 的教程中,正如您刚刚在部分解决方案中开始做的那样。
如何使用 libcst 模块替换某些 Python 代码 file_content 中名为 add_third 的函数的文档字符串?
使用 libcst.CSTTransformer 进行导航:
libcst.FunctionDef
)licst.SimpleString
)import libcst
class DocUpdater(libcst.CSTTransformer):
"""Upodate the docstring of the function `add_three`"""
def __init__(self) -> None:
super().__init__()
self._docstring = ''
def visit_FunctionDef(self, node: libcst.FunctionDef) -> Optional[bool]:
"""Trying to find the node defining function `add_three`,
and get its docstring"""
if node.name.value == 'add_three':
self._docstring = f'"""{node.get_docstring(clean=False)}"""'
"""Unfortunatly, get_docstring doesn't return the exact docstring value"""
return True
return False
def leave_SimpleString(
self, original_node: libcst.SimpleString, updated_node: libcst.SimpleString
) -> libcst.BaseExpression:
"""Trying to find the node defining the docstring
of your function, and update the docstring"""
if original_node.value == self._docstring:
return updated_node.with_changes(value='"""My new docstring"""')
return updated_node
最后:
test = r'''
import foo
from a.b import foo_method
class C:
def add_three(self, x):
"""Some first line documentation
Some second line documentation
Args:something.
"""
return foo_method(x)
def do_another_thing(y: list[str]) -> int:
"""Bike"""
return 1
'''
cst = libcst.parse_module(test)
updated_cst = cst.visit(DocUpdater())
print(updated_cst.code)
输出:
import foo
from a.b import foo_method
class C:
def add_three(self, x):
"""My new docstring"""
return foo_method(x)
def do_another_thing(y: list[str]) -> int:
"""Bike"""
return 1