评论 Python 脚本以编程方式。

huangapple go评论51阅读模式
英文:

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&trade; 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)

  # &lt;a href=&quot;https://libcst.readthedocs.io/en/latest/nodes.html#libcst.Module.visit&quot;&gt;.visit()&lt;/a&gt; calls the visitor&#39;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) -&gt; cst.CSTNode:
    &#39;&#39;&#39;
    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: &#39;aetest.Testcase&#39;
    &#39;&#39;&#39;

    match node:
      case cst.ClassDef(
        name = cst.Name(value = self._class_name),
        bases = [
          cst.Arg(
            value = cst.Attribute(
              value = cst.Name(value = &#39;aetest&#39;),
              attr = cst.Name(value = &#39;Testcase&#39;)
            )
          )
        ]
      ):
        # Replace any such block with a multiline string
        # containing the original source code.
        return cst.SimpleString(
          value = f&quot;&#39;&#39;&#39;{self._module.code_for_node(node)}&#39;&#39;&#39;&quot;
        )
      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(&#39;filename.py&#39;).comment_out(&#39;IPSEC&#39;)

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():
  ...
&#39;&#39;&#39;
class IPSEC(aetest.Testcase):
  ...
&#39;&#39;&#39;	
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, &#39;r&#39;) as f:
        lines = f.readlines()

    profile = f&quot;class {profile}(aetest.Testcase):&quot;
    comment = False
    print(f&quot;looking for {profile}&quot;)
    for i in range(len(lines)):
        if  lines[i].strip() == profile in lines[i]:
             comment = True
        elif comment and lines[i].startswith(&quot;class&quot;):
             comment = False
    
        if comment:
             lines[i] = &#39;# &#39; + lines[i]
    
    with open(file_name, &#39;w&#39;) as f:
        f.writelines(lines)

Some tips that I should mention:

  1. 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!
  2. Use with to open a file and read/write in them as it will close and save them automatically.
  3. Try doing everything in one loop as it is much faster!
  4. 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, &#39;w&#39;) as f:
found = 0
for line in lines:
if found == 0 and f&quot;class {profile}(aetest.Testcase):&quot; in line:
line = &#39;#&#39; + line
found = 1
elif found == 1:
if &#39;class&#39; in line:
found = 2
else:
line = &#39;#&#39; + 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, &#39;w&#39;) as f:
found = False
buffer = []
for line in lines:
if f&quot;class {profile}(aetest.Testcase):&quot; in line:
found = True
elif found:
if not line.strip():
buffer.append(line)
continue
elif &#39;class&#39; in line:
found = False
for l in buffer:
f.write(&#39;# &#39;*found + l)
buffer = []
f.write(&#39;# &#39;*found + line)

huangapple
  • 本文由 发表于 2023年7月27日 20:04:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76779557.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定