英文:
Comment python script programatically
问题
我的脚本文件中有许多类:
def func1():
...
class IPSEC(aetest.Testcase):
....
class IPSEC_DPI(aetest.Testcase):
....
class IPSEC_FNF(aetest.Testcase):
....
.
.
more classes
.
.
class all_results(aetest.Testcase):
....
`all_results`是最后一个类。
我需要创建一个函数,该函数接受类名和上述脚本的文件路径,并注释掉特定的类。`all_results`永远不会被注释掉。我知道这很奇怪,但目前的要求就是这样。
我创建了下面的函数,但它失败了。有没有其他简短的方法来实现同样的功能?
```python
def comment_lines(file_name, profile):
lines = open(file_name, 'r').readlines()
start=None
end=None
i=0
print(f"looking for class {profile}(aetest.Testcase):")
for i in range(0, len(lines)):
if f"class {profile}(aetest.Testcase):" in lines[i]:
start=i
break
i+=1
for i in range(start+1, len(lines)):
if f"class " in lines[i]:
end=i-2
break
i+=1
for i in range(start, end+1):
lines[i] = "#" + lines[i]
out = open(file_name, 'w')
out.writelines(lines)
out.close()
英文:
My script file has lots of classes:
def func1():
...
class IPSEC(aetest.Testcase):
....
class IPSEC_DPI(aetest.Testcase):
....
class IPSEC_FNF(aetest.Testcase):
....
.
.
more classes
.
.
class all_results(aetest.Testcase):
....
all_results
is the last class.
I need to create a function that accepts the class name and file path of the script above and comment that particular class. all_results will never be commented. I know its weird but thats the requirement as of now.
I created the function below, but it fails. Any other short method to achieve the same?
def comment_lines(file_name, profile):
lines = open(file_name, 'r').readlines()
start=None
end=None
i=0
print(f"looking for class {profile}(aetest.Testcase):")
for i in range(0, len(lines)):
if f"class {profile}(aetest.Testcase):" in lines[i]:
start=i
break
i+=1
for i in range(start+1, len(lines)):
if f"class " in lines[i]:
end=i-2
break
i+=1
for i in range(start, end+1):
lines[i] = "#" + lines[i]
out = open(file_name, 'w')
out.writelines(lines)
out.close()
答案1
得分: 2
使用libcst
和Python 3.10中引入的结构化模式匹配功能以正确的方式进行操作。如果不能升级到3.10,则可以使用if
/elif
/else
块,但这要求读者自行处理。
import libcst as cst
# 命名很难
class ClassFile:
def __init__(self, file_name: str):
self._file_name = file_name
def comment_out(self, class_name):
with open(self._file_name, 'r+') as file:
code = file.read()
tree = cst.parse_module(code)
# <a href="https://libcst.readthedocs.io/en/latest/nodes.html#libcst.Module.visit">.visit()</a>调用访问者的方法
# 每次访问和离开一个节点时。
new_tree = tree.visit(ClassDefCommenter(tree, class_name))
file.seek(0)
file.write(new_tree.code)
# 我们需要的访问者是<cst.CSTTransformer>的子类的实例:
class ClassDefCommenter(cst.CSTTransformer):
def __init__(self, module: cst.Module, class_name: str):
self._module = module
self._class_name = class_name
def leave_ClassDef(self, node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.CSTNode:
'''
此方法接受两个参数:
- 原始节点,和
- 该节点的深拷贝(要插入新树的)。
我们需要找到一个类定义块,其:
- .name 是给定的类名
- .bases 是一个只有一个参数的序列:'aetest.Testcase'
'''
match node:
case cst.ClassDef(
name = cst.Name(value = self._class_name),
bases = [
cst.Arg(
value = cst.Attribute(
value = cst.Name(value = 'aetest'),
attr = cst.Name(value = 'Testcase')
)
)
]
):
# 用包含原始源代码的多行字符串替换任何这样的块。
return cst.SimpleString(
value = f"'''\n{self._module.code_for_node(node)}\n'''\n"
)
case _:
# 否则,不做任何处理。
return updated_node
# 为了简化和解释,我使用多行字符串语法而不是注释。如果有嵌套的三引号,您可能需要转义它们。
# 试试:
ClassFile('filename.py').comment_out('IPSEC')
这个文件:
class aetest:
class Testcase:
...
def func1():
...
class IPSEC(aetest.Testcase):
...
class IPSEC_DPI(aetest.Testcase):
...
class IPSEC_FNF(aetest.Testcase):
...
class all_results(aetest.Testcase):
...
...将被修改为:
class aetest:
class Testcase:
...
'''
class IPSEC(aetest.Testcase):
...
'''
class IPSEC_DPI(aetest.Testcase):
...
class IPSEC_FNF(aetest.Testcase):
...
class all_results(aetest.Testcase):
...
英文:
Do It The Right Way™ with libcst
and the structural pattern matching feature introduced in Python 3.10. If updating to 3.10 is not a choice, if
/elif
/else
blocks can be used instead, but that is an exercise for the reader.
<pre><code>import libcst as cst
Naming is hard
class ClassFile:
def init(self, file_name: str):
self._file_name = file_name
def comment_out(self, class_name):
with open(self._file_name, 'r+') as file:
code = file.read()
tree = cst.parse_module(code)
# <a href="https://libcst.readthedocs.io/en/latest/nodes.html#libcst.Module.visit">.visit()</a> calls the visitor's methods
# every time it visits and leaves a node.
new_tree = tree.visit(ClassDefCommenter(tree, class_name))
file.seek(0)
file.write(new_tree.code)
</code></pre>
The visitor we need is an instance of a subclass of cst.CSTTransformer
:
class ClassDefCommenter(cst.CSTTransformer):
def __init__(self, module: cst.Module, class_name: str):
self._module = module
self._class_name = class_name
def leave_ClassDef(self, node: cst.ClassDef, updated_node: cst.ClassDef) -> cst.CSTNode:
'''
This method takes in two argument:
- The original node, and
- a deep copy of that node (to be inserted to the new tree).
We need to find a class definition block whose:
- .name is the given class name
- .bases is a sequence with only one argument: 'aetest.Testcase'
'''
match node:
case cst.ClassDef(
name = cst.Name(value = self._class_name),
bases = [
cst.Arg(
value = cst.Attribute(
value = cst.Name(value = 'aetest'),
attr = cst.Name(value = 'Testcase')
)
)
]
):
# Replace any such block with a multiline string
# containing the original source code.
return cst.SimpleString(
value = f"'''{self._module.code_for_node(node)}'''"
)
case _:
# Else, do nothing.
return updated_node
For simplicity and ease of explanation, I used multiline string syntax instead of comment. You might want to escape nested triple quotes, if any.
Try it:
ClassFile('filename.py').comment_out('IPSEC')
This file:
class aetest:
class Testcase:
...
def func1():
...
class IPSEC(aetest.Testcase):
...
class IPSEC_DPI(aetest.Testcase):
...
class IPSEC_FNF(aetest.Testcase):
...
class all_results(aetest.Testcase):
...
...will be modified to:
class aetest:
class Testcase:
...
def func1():
...
'''
class IPSEC(aetest.Testcase):
...
'''
class IPSEC_DPI(aetest.Testcase):
...
class IPSEC_FNF(aetest.Testcase):
...
class all_results(aetest.Testcase):
...
答案2
得分: 0
如何?
英文:
How about this?
def comment_lines(file_name, profile):
with open(file_name, 'r') as f:
lines = f.readlines()
profile = f"class {profile}(aetest.Testcase):"
comment = False
print(f"looking for {profile}")
for i in range(len(lines)):
if lines[i].strip() == profile in lines[i]:
comment = True
elif comment and lines[i].startswith("class"):
comment = False
if comment:
lines[i] = '# ' + lines[i]
with open(file_name, 'w') as f:
f.writelines(lines)
Some tips that I should mention:
- You don't need to define the variable you want to use in a for-loop before like other languages (C, C++, Java, etc) Python will do it for you!
- Use
with
to open a file and read/write in them as it will close and save them automatically. - Try doing everything in one loop as it is much faster!
- Use or update variables like
profile
before loop to make the loop faster!
答案3
得分: 0
另一种使用三态标志“found”的实现:
def comment_lines(file_name, profile):
with open(file_name) as f:
lines = f.readlines()
with open(file_name, 'w') as f:
found = 0
for line in lines:
if found == 0 and f"class {profile}(aetest.Testcase):" in line:
line = '#' + line
found = 1
elif found == 1:
if 'class' in line:
found = 2
else:
line = '#' + line
f.write(line)
另一种实现稍微长一些,但不会注释目标类定义结束后的空行(仍然会注释在该定义内的空行);这是通过缓冲空行并在遇到非空行后才将它们写入文件来实现的:
def comment_lines(file_name, profile):
with open(file_name) as f:
lines = f.readlines()
with open(file_name, 'w') as f:
found = False
buffer = []
for line in lines:
if f"class {profile}(aetest.Testcase):" in line:
found = True
elif found:
if not line.strip():
buffer.append(line)
continue
elif 'class' in line:
found = False
for l in buffer:
f.write('# ' * found + l)
buffer = []
f.write('# ' * found + line)
英文:
Another implementation using a tri-state flag found
:
def comment_lines(file_name, profile):
with open(file_name) as f:
lines = f.readlines()
with open(file_name, 'w') as f:
found = 0
for line in lines:
if found == 0 and f"class {profile}(aetest.Testcase):" in line:
line = '#' + line
found = 1
elif found == 1:
if 'class' in line:
found = 2
else:
line = '#' + line
f.write(line)
And another implementation, slightly longer, but that doesn't comment blank lines after the end of the target class definition (it still comments blank lines inside said definition); this is achieved by bufferizing blank lines and writing them to the file only after a not-blank line has been encountered:
def comment_lines(file_name, profile):
with open(file_name) as f:
lines = f.readlines()
with open(file_name, 'w') as f:
found = False
buffer = []
for line in lines:
if f"class {profile}(aetest.Testcase):" in line:
found = True
elif found:
if not line.strip():
buffer.append(line)
continue
elif 'class' in line:
found = False
for l in buffer:
f.write('# '*found + l)
buffer = []
f.write('# '*found + line)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论