PyChecker 在游戏引擎环境下的使用

           PyChecker 在游戏引擎环境下的使用

             Horin|贺勤
        Email: horin153@msn.com
        Blog: http://blog.csdn.net/horin153/

    用 Python 写程序,是否在软件发布版本中,还发现了一些低级的语法错误,比如变量名拼写错误、使用了没有导入的模块?是否担心因为测试逻辑覆盖分枝不完全,而影响程序的健壮性?是否因为简单的语法错误,而降低了开发、测试效率?
    如果有一个肯定的回答,请使用 PyChecker!


一、PyChecker 的简单介绍

    PyChecker 是一个对 python 代码进行静态语法分析的工具。不需要实际运行脚本,就可以发现源代码中的一些 bugs,并给出很多很不错的建议。但因为动态解释语言的本质,有些警告可能不正确。尽管 PyChecker 给出的 warnings 可能太多,甚至可能错误,但也可以根据实际情况进行采纳。


1.1 PyChecker 可以检查的问题

    PyChecker 可以检查以下问题(copy 自官方说明文档):
 - No global found (e.g., using a module without importing it)
 - Passing the wrong number of parameters to functions/methods/constructors
 - Passing the wrong number of parameters to builtin functions & methods
 - Using format strings that don't match arguments
 - Using class methods and attributes that don't exist
 - Changing signature when overriding a method
 - Redefining a function/class/method in the same scope
 - Using a variable before setting it
 - self is not the first parameter defined for a method
 - Unused globals and locals (module or variable)
 - Unused function/method arguments (can ignore self)
 - No doc strings in modules, classes, functions, and methods

    概括来说,主要包括:
 - 使用了没有导入/没有定义的 global 变量;
 - 使用了不存在的 class 方法或属性,比如拼写错误;
 - 函数调用参数个数不对;
 - 格式化字符串时变量不匹配;
 - 改写(override)基类方法时改变了签名,如减少了最后的默认参数;
 - 使用了未赋值的变量。


1.2 PyChecker 的分析过程

    如果用 Importing PyChecker 方式检查,需要做以下处理:
    如果开启了 PyChecker 的检查功能(不设置环境变量 PyChecker_DISABLED),PyChecker 就会用自己的 __import__() 函数替换 __builtin__.__import__() 函数;因此,在 PyChecker 之前 import 的模块是不能检查的,仅有在其后 import 的模块才可以检查。
    在自己的 __import__() 函数中,PyChecker 首先用原始的 __builtin__.__import__() 来执行真正的 import 操作;如果需要检查,就把 import 操作返回的对象,传给内部的检查模块。

    内部检查模块根据 op-code,对 import 操作生成的 byte code 进行迭代分析,并生成最后的检查结果。


1.3 PyChecker 的局限

    PyChecker 有以下局限(copy 自参考资料 1):

 - Jython cannot be supported since it does not use CPython byte codes
 - Line numbers cannot be reported if the code has been optimized
 - Code block/branch/path analysis and determination of implicit returns are more difficult since the concepts from the original source code must be inferred from the byte code
 - Code must be imported which can be problematic for some code which requires setting up the proper environment (e.g., setting up sys.path, importing modules in specific order, etc.)
 - Comments cannot be used to supply hints, __PyChecker__ must be used


二、PyChecker 的使用

    首先下载最新的 PyChecker 包:
http://sourceforge.net/project/showfiles.php?group_id=24686
    下面以 pychecker-0.8.17.tar.tar 包为例说明。


2.1 命令行方式

step 1、安装 PyChecker

 - 解压安装包 pychecker-0.8.17.tar.tar
    运行 cmd.exe,运行以下命令:
 - cd pychecker-0.8.17
 - D:/Python24/python.exe setup.py install
 - 在 我的电脑 -> 属性 -> 高级 -> 环境变量 -> 系统变量 -> Path
   增加一个新值:D:/Python24/Scripts,以自动搜索到 pychecker.bat

   注:安装包名、PyChecker 解压后路径、Python 安装路径请以实际的为准。

step 2、检查

 - 运行 cmd.exe,运行以下方式的命令:
   pychecker testobj.py testobj2.py
   pychecker *.py
   pychecker -#50 -9 -v -b ['C_app'] testobj.py


2.2 代码中 Importing PyChecker 方式

    通过命令行运行 PyChecker,因为是运行标准的 Python 解释器,对使用了二进制扩展模块的 .py 文件,将无法检查(当然可以构造一个假的 .py 模块来代替扩展模块,以让 PyChecker 能够运行;但这实在不是一个好主意)。典型的就是引擎环境下的 Python 脚本文件。

    如果待查模块依赖于其他模块(可能在不同路径下),必须在开始就按照正确的顺序 import 这些模块。

    为了处理类似问题,此时就需要在代码中 import PyChecker 了。在代码的主模块中,导入 checker.py 模块;在 checker.py 后 import 的模块就可以被检查了。


三、游戏引擎的一些特点

    根据游戏的需要,引擎会提供多个 Python 扩展模块。

    为了简化程序,游戏引擎对 Python 的支持,一般是有一些限制的:
 - 不支持 package;
 - 仅支持已经 build 进引擎的 Python 的 builtin 模块;
 - 引擎中的 Python 解释器有自己的 op-code。


四、在引擎环境下使用 PyChecker

4.1 去除 package

    PyChecker 是以 package 的形式发布,而引擎不支持 package;所以第一步就是去除 package。

4.1.1 新写一个模块 pychecker.py 来代替包的功能

    这样就可以减少对 PyChecker 相关模块的修改,把需要改动的东西尽可能集中到新模块 pychecker.py 中来。新模块的代码如下:

# pychecker.py
#------------- code 1 begin -----------------------

# -*- coding: utf-8 -*-

## change the environment if necessary.
#import os
## command line options here:
##cmd_options = '-#10 -v -8 -b ["ui"]'
##cmd_options = '--no-override --no-local --no-argsused --no-varargsused'
##cmd_options = '-F F:/pychecker/pycheckrc'
#os.environ['PYCHECKER'] = cmd_options
## enable or disable pychecker.
#os.environ['PYCHECKER_DISABLED'] = '1'

# copy from pychecker/__init__.py
MAIN_MODULE_VERSION = 3

# import modules according to follow sequence.
from Warning import Warning as CWarning
import Warning as Warning
import msgs as msgs
import Config as Config
import function as function
import utils as utils
import OP as OP
import printer as printer
import Stack as Stack
import python as python
import CodeChecks as CodeChecks
import warn as warn
import checker as checker

#------------- code 1 end -------------------------

4.1.2 删除不需要的 .py 文件

    因为不再是 package,也不需要用图形界面来配置选项,所以删除以下模块:
__init__.py, options.py, OptionTypes.py。

4.1.3 修改相关模块的代码

    把相关模块(checker.py, utils.py, warn.py)的以下代码:
from pychecker.Warning import Warning
    替换为以下代码:
from pychecker import CWarning as Warning


4.2 Build 一个特殊的引擎

    PyChecker 是靠正则表达式进行语法检查的,并根据标准的 op-code 对 import 操作生成的 byte code 进行迭代分析;这就要求解释器支持 re.py 等相关模块,并使用标准的 op-code。


4.3 在需要的地方加载 PyChecker

    为方便调用,编写函数 run_pychecker():

#------------- code 2 begin -----------------------

def run_pychecker():
    """importing pychecker for checking if it was enabled."""

    # check only in debug mode.
    if __debug__:
        # add path for pychecker if necessary.
        # assume pychecker/ is a subdirectory in current working directory.
        import sys
        sys.path.append("pychecker/")
        sys.path.append("pychecker/lib/")
        # importing extension modules in binary firstly.
        #import C_app,C_ui
        import pychecker
        # restore the stdout & stderr because pychecker uses them.
        import outputwindow
        sys.stdout = outputwindow.OutputWindow()
        sys.stderr = outputwindow.OutputWindow()

#------------- code 2 end -------------------------

    然后,在代码的主模块的适当位置,调用该函数即可。


4.4 PyChecker 选项的配置

    PyChecker 有十分详细的命令行选项,安装后在命令行运行 pychecker -h 命令,可获取详细的选项说明。
    也可以通过配置文件 pycheckrc 直接编辑命令行选项。

    如果是采用 importing PyChecker 的方式,因为不能传递命令行参数了,就需要在代码中修改环境变量来实现,如:
    通过是否设置 PYCHECKER_DISABLED 环境变量,就可以禁用/启用检查功能;通过配置 PYCHECKER 环境变量,就可以传递检查选项。可参见 4.1.1 中的代码。

    采用 importing PyChecker 的方式时,选项配置的官方文档有错,请注意参照 4.1.1 的代码进行配置。
    当指定配置文件 pycheckrc 时,建议使用绝对路径,并采用斜杠分隔目录(用反斜杠解析易出错),如:
    os.environ['PYCHECKER'] = '-F F:/pychecker/pycheckrc'
    当禁用某一个选项时,务必加上前缀“--”,否则无法解析,正确写法如:
    os.environ['PYCHECKER'] = '--no-local'


五、一些补充说明

5.1 检查模块的顺序

    如果待查模块的 import 有依赖关系,或者存在交叉 import 现象,则必须按照正确的 import 顺序进行检查,以免错误。


5.2 函数内的 global 变量

    PyChecker 对 golbal 变量的检查还不够完善,如果只在函数内定义 global 变量,在检查时将报告没有在模块范围定义(Global variable (g_demo) not defined in module scope)。如果直接在函数中使用 global 变量,将报告没有发现 global 变量(No global (g_demo) found)。

    对于下面这种实际正确的代码(注意:g_demo = None 这行代码是注释了的),PyChecker 检查后还会运行错误:

#------------- code 3 begin -----------------------

#g_demo = None

class CDemo:
    def __init__(self):
        pass

def get_demo_ins():
    global g_demo
    try:
        print '[get_demo_ins] try scope'
        return g_demo
    except:
        print '[get_demo_ins] except scope'
        g_demo = CDemo()
    print '[get_demo_ins] function scope'
    return g_demo

def test_get_demo():
    print '[test_get_demo] begin'
    _demo = get_demo_ins()
    print '[test_get_demo] g_demo type is ' + str(type(_demo))
    print '[test_get_demo] g_demo content is ' + str(_demo)

#------------- code 3 end -------------------------

    如果在 import pychecker 后调用函数 test_get_demo(),运行结果如下:

[test_get_demo] begin
[get_demo_ins] try scope
[test_get_demo] g_demo type is <type 'str'>
[test_get_demo] g_demo content is g_demo

    g_demo 变量竟然是字符串类型,内容是变量名;这和正确逻辑生成的 CDemo 类对象相差太远了。
    如果在模块开始定义变量 g_demo = None,并用 if-else 来代替 try-except 就可解决该错误。

    如果禁用 PyChecker 后程序运行正确,启用后程序运行错误,很可能就是这个原因导致的脚本错误。


5.3 交叉 import 的模块

    两个模块互相 import,如下代码所示:

#------------- code 4 begin -----------------------

#----- demo.py -----
import demo3
g_demo = None

def get_demo_ins():
    global g_demo
    try:
        return g_demo
    except:
        g_demo = 'g_demo'
        return g_demo


#----- demo3.py -----
import demo

def check_demo():
    if demo.g_demo is None:     # No module attribute (g_demo) found
        print 'demo.g_demo is None'

    _demo = demo.get_demo_ins() # No module attribute (get_demo_ins) found

#------------- code 4 end -------------------------

    上述代码运行是正确的,但用 PyChecker 检查时,在 demo.py 中 import demo3 时,就开始分析 demo3 中的代码了,此时 demo 中 g_demo & get_demo_ins 当然是不存在的。这也算是 PyChecker 做静态语法分析时应该改进的一个流程吧。


5.4 其他工具的配合使用

    在 Python 程序开发过程中,可以配合使用以下工具:
 - 用 PyChecker 进行静态语法分析,使程序能够运行;
 - 用 unittest 进行单元测试,使程序运行正确;
 - 用 Profiler 进行优化,使程序运行快速。


参考资料

1, PyChecker: Finding Bugs in Other People's Programs, http://www.metaslash.com/brochure/ipc10.html
2, http://PyChecker.sourceforge.net/
3, PyChecker, unittest, and the Python Profiler, http://blogs.sun.com/wwalker/entry/PyChecker_unittest_and_the_python
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值