欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程语言 > python >内容正文

python

利用抽象语法树检查Python中“未定义”的变量名

发布时间:2025/7/25 python 46 豆豆
生活随笔 收集整理的这篇文章主要介绍了 利用抽象语法树检查Python中“未定义”的变量名 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

其实,Python是一种真正的动态语言,代码中的变量名本没有“声明”或“定义”的说法,语言本身也没有提供声明或定义变量的特殊语法(global除外)。对程序员来说,这是一种好处,也是一种危险,比如像下面这段代码:

count = total = 1
delta = 0.7
while total < 1000:
  total += delta * (count * count + delta * delta)
  dalta = delta * 1.1
  count *= dalta
print total

代码后面的dalta是delta拼写错误的结果,程序可以正确运行,也可以通过pychecker工具的检查,但其输出显然与预期的正确结果相差甚远。不少人认为像Perl中的strict或Visual Basic中的Option Explicit可以帮助程序员减少出现类似错误的几率(尽管我自己并不这么想),但在Python中,因为没有显式定义或声明变量名的语法,这种强制检查似乎较难下手——网上可以查到一些解决方案,但或者比较复杂,或者是用__slots__,decorator这样的机制来解决部分问题,用起来不太方便。

也许,利用parser或compiler包提供的抽象语法树(Abstract Syntax Tree)可以比较简单地解决这个问题。我大致写了一段名为strict.py的代码。为了将自己的程序改为强制声明变量的“安全代码”,我们只需要按照strict.py的要求,用 __decl__ = "name1 name2 ..." 这样简单的语法在使用前预先声明变量名即可。例如,可以在上面那段危险代码的开头加上:

__decl__ = "delta total count"

然后用strict.py检查这段代码(假设其文件名为test.py):

python strict.py test.py

我们可以在运行结果中看到:

File 'test.py', line 6: name 'dalta' is not declared.

瞧,很容易就把拼写错误的变量名 dalta 给找出来了——因为 dalta 这个名字没有预先“声明”。

strict.py也可以检查Class或Function等代码块内部的局部名字( __decl__ = "..." 这样的声明语句可以用在代码中的任何位置),可以识别from ... import、global或函数参数表等引入的名字。像下面这样的代码:

__decl__ = 'name1 name2 name3'

name1 = 1
name2 = 'Jack'
name3 = name1 + 3

def foo():
  global name1
  __decl__ = 'local_name1 local_name2'
  name1 += 4
  local_name1 = 1.2
  local_name2 = 'Mike'
  undeclared = 9

strict.py可以很快找出其中的undeclared是“未声明”的名字。

strict.py只检查那些作为赋值目标的名字(l-value),对于读取某个名字,调用某个函数名,通过 obj.attr 这样的语法访问对象的属性或成员等等情况,strict.py没有必要考虑——因为如果这些情况中出现了未定义的名称,编译或运行程序时就会报出错误来,不会造成潜在的危险隐患。

因为只是示例性质的代码,我只在Python 2.4.3的环境下测试过strict.py,也没有做更多复杂的测试。这段代码一定还有许多需要改进之处。先把strict.py的代码罗列在下面吧:


strict.py
------------------------------------------------------

import sys
import compiler

declaration_flag = "__decl__"

def find_undeclared_names(ast, frames, is_decl):

    next_frames = frames

    def add_name(name):
        frames[-1][name] = True

    def find_name(name):
        return frames[-1].has_key(name)

    def get_alias(name_pair):
        if name_pair[1] is None:
            return name_pair[0]
        else:
            return name_pair[1]

    if ast.__class__.__name__ == "AssName":
        if not is_decl[0] and ast.name == declaration_flag:
            is_decl[0] = True
        elif not find_name(ast.name):
            yield ast.name, ast.lineno

    elif ast.__class__.__name__ == "Global":
        map(add_name, ast.names)

    elif ast.__class__.__name__ == "From":
        if (ast.names[0][0] == "*"):
            mod = __import__(ast.modname)
            map(add_name, filter(lambda x:not x.startswith('_'), dir(mod)))
        else:
            map(add_name, map(get_alias, ast.names))

    elif ast.__class__.__name__ == "Const":
        if is_decl[0] and ast.value.__class__.__name__ == "str":
            map(add_name, ast.value.split())
            is_decl[0] = False

    elif ast.__class__.__name__ == "Function":
        next_frames = frames + [dict(map(lambda x: (x, True), ast.argnames))]

    elif ast.__class__.__name__ == "Class":
        next_frames = frames + [{}]

    for childNode in ast.getChildNodes():
        for x in find_undeclared_names(childNode, next_frames, is_decl):
            yield x

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print "Usage: python strict.py <python-source-file>"
    else:
        for name, line_no in /
                find_undeclared_names(compiler.parseFile(sys.argv[1]),
                                      [{}],
                                      [False]):
            print "File '%s', line %d: name '%s' is not declared." % /
                  (sys.argv[1], line_no, name)

转载于:https://www.cnblogs.com/xiaomaohai/archive/2006/05/14/6157194.html

总结

以上是生活随笔为你收集整理的利用抽象语法树检查Python中“未定义”的变量名的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。