设为首页 收藏本站
查看: 1071|回复: 0

[经验分享] 利用抽象语法树检查Python中“未定义”的变量名

[复制链接]

尚未签到

发表于 2017-5-8 07:24:27 | 显示全部楼层 |阅读模式
其实,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)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-374382-1-1.html 上篇帖子: 零基础学python-13.5 多迭代器vs单迭代器 下篇帖子: 模块用于Python Tutorial(十):浏览标准库(一) 模块用于
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表