如何在Python中获取"end-of-statement"的lineno

xis*_*xis 39 python abstract-syntax-tree

我正在尝试在Python中操作另一个脚本的脚本,要修改的脚本具有如下结构:

class SomethingRecord(Record):
    description = 'This records something'
    author = 'john smith'
Run Code Online (Sandbox Code Playgroud)

ast用来找到description行号,并使用一些代码来更改原始文件,其中新的描述字符串基于行号.到现在为止还挺好.

现在唯一的问题description偶尔是一个多行字符串,例如

    description = ('line 1'
                   'line 2'
                   'line 3')
Run Code Online (Sandbox Code Playgroud)

要么

    description = 'line 1' \
        'line 2' \
        'line 3'
Run Code Online (Sandbox Code Playgroud)

我只有第一行的行号,而不是以下行.所以我的单行替代品会这样做

    description = 'new value'
        'line 2' \
        'line 3'
Run Code Online (Sandbox Code Playgroud)

并且代码被破坏了.我想如果我知道开始和结束的行号/ description赋值行数我可以修复我的代码来处理这种情况.如何使用Python标准库获取此类信息?

Ira*_*ter 7

我看了其他的答案; 当你真正的问题是修改代码时,似乎人们正在做后空翻来解决计算行号的问题.这表明基线机制并没有以您真正需要的方式帮助您.

如果你使用程序转换系统(PTS),你可以避免很多这些废话.

一个好的PTS会将您的源代码解析为AST,然后让您应用源级重写规则来修改AST,并最终将修改后的AST转换回源文本.通常PTS接受基本上这种形式的转换规则:

   if you see *this*, replace it by *that*
Run Code Online (Sandbox Code Playgroud)

[构建AST的解析器不是PTS.他们不允许这样的规则; 你可以编写特殊的代码来破解树,但这通常很尴尬.他们不做AST来源文本再生.]

(我的PTS,见bio,叫)DMS是一个可以实现这一目标的PTS.通过使用以下重写规则,可以轻松完成OP的具体示例:

 source domain Python; -- tell DMS the syntax of pattern left hand sides
 target domain Python; -- tell DMS the syntax of pattern right hand sides

 rule replace_description(e: expression): statement -> statement =
     " description = \e "
  ->
     " description = ('line 1'
                      'line 2'
                      'line 3')";
Run Code Online (Sandbox Code Playgroud)

一个转换规则被赋予一个名称replace_description,以区别于我们可能定义的所有其他规则.规则参数(e:表达式)表示模式将允许源语言定义的任意表达式. statement-> statement表示规则将源语言中的语句映射到目标语言中的语句; 我们可以使用提供给DMS的Python语法中的任何其他语法类别.的"这里使用的是一个metaquote,用来区分规则语言的语法形成被摄体语言的语法的第二个.- > 分隔的源极图案从目标图案那个.

您会注意到没有必要提及行号.PTS通过使用用于解析源文件的相同解析器实际解析模式,将规则表面语法转换为相应的AST.为模式生成的AST用于实现模式匹配/替换.因为这是由AST驱动的,所以原始代码的实际布局(间距,换行符,注释)不会影响DMS的匹配或替换能力.注释不是匹配的问题,因为它们附加到树节点而不是树节点; 它们保存在改造后的程序中.DMS确实捕获所有树元素的行和精确列信息; 只是不需要实现转换.使用该行/列信息,DMS在输出中也保留代码布局.

其他PTS提供了大致相似的功能.


Oha*_*tan 6

作为解决方法,您可以更改:

    description = 'line 1' \
              'line 2' \
              'line 3'
Run Code Online (Sandbox Code Playgroud)

至:

    description = 'new value'; tmp = 'line 1' \
              'line 2' \
              'line 3'
Run Code Online (Sandbox Code Playgroud)

等等

这是一个简单的改变,但确实产生了丑陋的代码.

  • 所以你可以把它称为其他垃圾名称,比如`thisisgarbagevariablemadebyxisscript777` (12认同)
  • 如果您不能假设存在某些垃圾变量名称,则应检查存在哪些名称并生成不同的名称. (3认同)

duk*_*ave 5

end_lineno 从 Python 3.8开始可用。

  • 在 2020 年,**这是规范的答案。** 但请注意,[`ast.get_source_segment()` 函数](https://docs.python.org/3/library/ast.html#ast.通常应该调用 Python 3.8 首次引入的 get_source_segment)。由于很少使用可选的行终止符“;”,仅根据行号计算字符间隔对于边缘情况代码来说肯定会失败。行*和*列号都必须考虑。 (3认同)

iva*_*anl 3

事实上,您需要的信息并未存储在ast. 我不知道您需要什么的详细信息,但看起来您可以使用tokenize标准库中的模块。这个想法是每个逻辑Python语句都以一个标记结束NEWLINE(也可以是分号,但据我了解这不是你的情况)。我用这样的文件测试了这种方法:

# first comment
class SomethingRecord:
    description = ('line 1'
                   'line 2'
                   'line 3')

class SomethingRecord2:
    description = ('line 1',
                   'line 2',
                   # comment in the middle

                   'line 3')

class SomethingRecord3:
    description = 'line 1' \
                  'line 2' \
                  'line 3'
    whatever = 'line'

class SomethingRecord3:
    description = 'line 1', \
                  'line 2', \
                  'line 3'
                  # last comment
Run Code Online (Sandbox Code Playgroud)

这就是我建议要做的:

import tokenize
from io import BytesIO
from collections import defaultdict

with tokenize.open('testmod.py') as f:
    code = f.read()
    enc = f.encoding

rl = BytesIO(code.encode(enc)).readline
tokens = list(tokenize.tokenize(rl))

token_table = defaultdict(list)  # mapping line numbers to token numbers
for i, tok in enumerate(tokens):
    token_table[tok.start[0]].append(i)

def find_end(start):
    i = token_table[start][-1]  # last token number on the start line
    while tokens[i].exact_type != tokenize.NEWLINE:
        i += 1
    return tokens[i].start[0]

print(find_end(3))
print(find_end(8))
print(find_end(15))
print(find_end(21))
Run Code Online (Sandbox Code Playgroud)

这打印出:

5
12
17
23
Run Code Online (Sandbox Code Playgroud)

这似乎是正确的,您可以根据您的具体需要调整此方法。tokenize比 更详细,ast但也更灵活。当然,最好的方法是将它们用于任务的不同部分。


编辑:我在Python 3.4中尝试过这个,但我认为它也应该适用于其他版本。