2.3 为游戏脚本创建一个“安全沙盘”
Matthew Walker,NCSoft Corporation
mwalker@softhome.net
本文的源代码包含在所附的光盘中。
|
游 |
戏的开发需要一个稳健的服务器架构来满足MMP游戏在技术和支持方面的需求。然而,根据一般规律,持续地开发动态内容会加大引入错误的风险,这些错误可能会使入侵者有机可乘,破坏游戏的平衡。降低这种风险的方法之一就是建立一个“安全沙盘”(safe sandbox),这样游戏设计人员就可以编写代码实现他们的创造性想法,而不必担心会触及那些敏感的子系统。本文探讨了怎样使用高级程序设计语言Python来创建一个“安全”沙盘以实现一个封装的消息驱动环境,从而使设计人员“可以”把精力集中在游戏模式的开发上。
2.3.1 脚本语言与MMP开发
像Python、JavaScript、Perl和UnrealScript[Sweeney98]这样的脚本语言都可运行于虚拟机中。本质上来说,虚拟机是一个CPU模拟程序。它可以用来执行那些与平台无关的字节码(byte code)表示的程序,而不是那些由C或C++之类的语言生成的针对特定处理器的机器码[Kohlbrenner99]。在程序设计语言中使用这样的架构可以隐藏很多与内存管理、资源管理以及其他与操作系统功能相关的复杂细节。隔离这些细节还可以把由编程错误引起的问题限制在一定范围内。
脚本语言使用弱数据类型,这意味着变量可以在不同时刻包含不同类型的数据;脚本语言使用自动类型转换,这使得某种类型的数据可以方便地转换为另一种类型,譬如说从数字转换为字符串。这给开发函数/方法接口以及数据结构带来很大的灵活性。最后,这些语言通常还提供了像字符串、线形表、词典之类的高级对象,这可以使基于它们的开发更为有效[Ousterhour98]。
MMP游戏是基于服务的产品,它不仅需要持续地保持可靠性,还需要经常更新动态内容。这意味着在MMP游戏项目的整个生命期内,游戏开发人员不仅要对其进行持续的改进和修正,还要保持一份可维护的稳定代码。这两个目标常常是对立的,因为通常对软件的频繁修改意味着会稳定性的丧失。脚本语言的特性使它们在那些需要同时满足这两个目标的应用中非常有价值。
使用Python还是实现自己的脚本语言
开发小组度身定制他们自己的脚本语言在游戏行业已经成为一种传统。UnrealScript和Quake C是两个比较知名的例子。然而,设计、实现、调试并扩展一个程序设计语言是否能够更有效地使用开发小组的时间呢?从一个定制的语言中,游戏开发人员就可以精确地获得他们所需要的特性。但是有几个缺点抵消了这一优势:
· 用定制语言开发的游戏总是落后于对这个语言本身的开发,这无疑会给开发进度带来风险;
· 整个项目中,至少必须有一个全职程序员专职对这个语言进行开发和维护;
· 定制的专用语言会对它的用途作一些假设,最终这些假设可能会被证实为不够灵活因而不能满足项目发展的需要;
· 定制的通用语言需要精心的设计并且很可能会超出项目的需求范围,从长期来看,它可能会由于规模过大、涉及面过广而变得难以维护;
· 任何定制语言对于大多数程序员来说都是陌生的,并且它不像主流语言那样具有很多培训资源,譬如说参考书和网站,这会限制熟练的程序员在项目中的产出。
因此程序员应该使用那些现存的、已经使用了一段时间并且被很多应用验证过的通用脚本语言。由于下述原因[Python01],本文推荐使用Python。
· 它的语法非常简单易学。
· 大部分使用C/C++来实现,这使得其执行速度相对来说比较快。
· 可以很好地和C、C++及其他编译语言整合。
· 它是面向对象的,不仅支持类、单一和多重继承,还具有定义良好的作用域和名字空间,这有助于养成良好的设计习惯。
· 用它来完成一个给定功能所需要的代码行数比其他大多数语言少。
· 它有一套丰富的内建库,提供了包括文字处理、数据库交互、进程控制、文件输入输出、网络、加密等功能。
2.3.2 使用沙盘的理由
在对任何一个生命期较长的软件项目进行维护和改进的过程中,开发人员常常会因为贪图方便或是某些不成熟的“创造”行为而为系统引入风险。就代码完整性而言,最大的危险往往存在于那些需要经常修改的代码中。在整个游戏服务的生命期内,这些代码往往和实现游戏规则以及定义游戏中有趣部分的上层系统有关。开发人员需要对游戏进行频繁的修改(进行游戏平衡、加入特殊事件、加入新的物品和技能等)来保持游戏的趣味性。
与之相反,关键的底层系统(例如:网络代码、数据库接口、物理、移动、骇客检测等)应该很少需要修改并且每次修改都必须非常小心。避免在更新游戏系统时意外地影响到这些关键系统尤为重要。上述命题的逆命题几乎同样重要:仅仅因为害怕引入这些不稳定因素而不对游戏系统进行修改也是不应该的。
1.创造性天赋并不等同于技术才能
MMP项目常常寄希望于那些技术经验有限的游戏设计人员通过编码来实现游戏模式中的规则和关卡。虽然作为具有高度创造力的、非常有价值的设计人员,他们知道怎样使游戏变得更有趣,但是他们通常并不知道应该怎样设计游戏才能使游戏更稳健并且更容易维护。指望设计人员意识到诸如最小化依赖、定义一致的接口、抽象等概念的重要性是不现实的。
2.一个用于实验的安全空间
下面开始讨论安全沙盘的概念。这是一个“人造”术语,它表示了一个受保护的空间。在这个空间里,游戏设计人员和内容开发人员可以任意地进行修改,而不必担心他们的工作会破坏这个系统。这个安全沙盘的目的就是要把用于实验的代码同关键的底层部分完全隔离开,这样使得只有在需要的时候才能通过良好定义的接口对特定的子系统进行访问。下面是在游戏编码和内容维护的实践中应该避免的一些做法:
· 在正常的框架以外任意地创建和销毁游戏对象;
· 直接控制游戏中对象的移动、位置和方向;
· 为了适合特殊的关卡而对AI或寻路(path-finding)算法进行有条件的调整;
· 绕开游戏数据库并且把游戏数据写到本地文件系统中;
· 直接打开一个连接到其他服务端/客户端的套接字(socket),而不是使用现有的消息系统;
· 直接使用嵌入式结构化查询语言(Embeded SQL)来查询或更新数据库,而不是通过现有的数据更新和读取接口;
· 向游戏客户端发送任意的文本消息;
· 对某个现存子系统的使用与其设计目的不一致。
通常,最初的游戏开发过程不应该允许这些行为的发生。不过这并不会限制后续开发人员这样使用系统,他们可能是想通过一些捷径来获得一个可以立即运行的服务。
2.3.3 安全沙盘的设计
在最初设计中就把安全沙盘作为整个架构的一部分有助于避免很多在开发后期会遇到的问题。
1.Python的受限运行(Restricted Execution)特性
安全沙盘使用了一项Python独有的特性:受限运行。它由rexec和Bastion模块[Python02]组成。rexec模块可以在游戏服务端的上下文中创建一个独立的具有自己名字空间的Python运行环境。游戏开发人员可以使用rexec来限制在沙盘中可以使用哪些Python提供的服务,也可以控制底层系统中有哪些在沙盘中是可用的。通过使用Bastion模块,游戏开发人员可以用一个保护性的代理(protective proxy)来包装那些他们想提供给沙盘的类,使得沙盘只能访问其正常接口的一个子集。
2.安全沙盘的关键元素
每个游戏服务都会定义自己的架构规则和需求。因此本文只能为这个概念提供一个纯理论化的典型实现。安全沙盘具有下面这些主要设计元素。
1.SandboxRExec类
这个类从Python的rexec模块中的RExec类继承而来,它定制了缺省的受限执行环境以满足游戏的需要。
Python具有一个内建的sys.path属性,它使得解释器可以在收到载入模块的请求时找到模块。SandboxRExec模块定义了_GetSandboxPath函数,它会返回沙盘中所支持的模块搜索路径。这个路径用来替代内建的sys.path属性。
def _GetSandboxPath():
return ['/usr/lib/python2.2',
'/usr/lib/python2.2/plat-linux-i386',
'/usr/lib/python2.2/site-packages',
'/usr/lib/python2.2/site-packages/Numeric',
'/usr/local/lib/gameserver/'] # our server's area
RExec基类维护了一些元组(tuple)属性,用来表示允许访问或拒绝访问的模块。元组是Python的一种数据结构,本质上它是一个不变的线性表。在SandboxRExec类中,程序员简单地使用自己的模块列表来覆盖(override)这些元组。以_names为后缀的属性表示相应的关键字和函数能够使用(用ok_为前缀)还是不能使用(用nok_为前缀)。那些具有_module后缀的属性表示整个模块都受到这个环境的控制。最后,用_GetSandboxPath()函数来初始化ok_path属性。
class SandboxRExec(rexec.RExec):
nok_builtin_names = rexec.RExec.nok_builtin_names + \
('compile','delattr','execfile','globals',
'input','locals','raw_input','vars')
ok_builtin_modules = ('math','operator','time')
ok_path = _GetSandboxPath() # load the valid path
ok_posix_names = () # no os module access
ok_sys_names = () # no sys module access
ok_library_modules = \
('types','operator','copy','string',
'math','cmath','random','time')
程序员添加了他们自己的属性ok_server_modules,它列出了允许访问的游戏服务端模块。另一个定制属性ok_packages包含了系统中那些从任意模块都可以引入(import)的包(package)。包是Python中把相关模块封装在一起的机制。
ok_server_modules = \
('safesandbox','events','errorhandler','log')
ok_packages = ('sandboxmodules',)
最后,把所有可用的模块一起放入可以被基类识别的ok_modules元组中。
ok_modules = ok_builtin_modules + \
ok_library_modules + ok_server_modules
为了让某些Python核心函数以某种特定的方式运行,基类RExec为这些Python核心函数提供了一系列钩子,包括r_exec()、r_eval()、r_execfile()、r_import()、r_reload()和r_unload()。这些钩子与Python中那些去掉了前缀r_的函数相对应,它们将取代对应函数而被调用。游戏开发人员希望对文件系统的任何访问都被禁止,所以他们覆盖了r_open()函数,这样一旦在受限环境中调用open()函数就会抛出一个异常。
def r_open(self,filename,mode=None,bufsize=None):
raise IOError,'No access to filesystem is allowed.'
为了让特定的模块能够在受限环境中运行,游戏开发人员创建SandboxExec的一个实例并且通过它的r_import()方法来引入模块,而不是象通常那样使用Python的import关键字。这个方法通过检查本节前面定义的元组来决定那些能够被引入模块。
def r_import(self,modulename):
okToImport = 0
if modulename in self.ok_modules:
okToImport = 1
else:
# special test to see if module is a
# submodule of an allowed package
for pkg in self.ok_packages:
if len(modulename) >= len(pkg):
if modulename[:len(pkg)] == pkg:
okToImport = 1
if okToImport:
# call base class implementation
mod = rexec.RExec.r_import(self,modulename)
# if module is in dotted-path notation,
# must get the left-most name
components = string.split(modulename,'.')
for comp in components[1:]:
mod = getattr(mod,comp)
return mod
raise ImportError('Restricted: %s' % (modulename,))
在受限环境中运行的代码只能引入和执行那些被r_import允许的模块和函数。
2.Bastion()和BastionClass
Bastion()函数是一个工厂(factory),它可以创建BastionClass的实例,而BastionClass可以通过包装对象来避免未经授权的访问。这些功能由Python的Bastion模块直接提供,在使用时不需要对其进行任何修改。Bastion化(Bastion-ized)的类的缺省行为是阻止对任何数据成员以及以下划线(_)为前缀的方法的访问。
使用这个缺省的实现,给定一个实例x,就可以合法地进行调用。
loc = x.GetLocation()
然而,如果试图调用:
x._SetLocation(1.5,3.7,0.0)
就会得到一个Python异常:
AttributeError: _SetLocation
通过提供一个可选的过滤函数作为Bastion()函数的第二个参数。就可以定制这个行为。这个函数必须接受一个包含了属性名称的字符串为参数,并且在允许访问这个属性的情况下返回true,在不允许访问的情况下返回false。这种方式可以对那些受保护的类应该怎样向受限环境导出方法作出规定。可以使用下面的形式来实现这样的过滤函数。
def LegalMethodFilter(name):
if name[:5] == ‘game_’:
return 1
return 0
然后,当用这个过滤函数创建一个受保护对象时,可以这样调用Bastion函数。
ob = SomeObject()
safeob = Bastion.Bastion(ob,filter=LegalMethodFilter)
现在,游戏开发人员可以在受限环境里调用safeob中任何以字符串“game_”为前缀的方法,其他函数将被禁止调用。
|
|
如果需要明确指定哪些方法是可访问的,就可以覆盖缺省的BastionClass以显式地支持列出每一个导出方法,而不是像以上的例子中那样使用一个名字约定。程序员甚至可以编写自己的Bastion函数实现来支持对某些特定属性的直接访问同时禁止访问其他属性。读者可参考所附光盘中的Python Bastion.py模块(在Python发布的lib目录中)以获得更多的信息。 |
3.SafeSandbox基类
SafeSandbox是一个抽象基类,游戏脚本的作者可以由此派生出他们自己的实现类。这些派生类的作用域可以与游戏世界中的某个环境一致,也可以和需要遵循某些规则或是某些预期行为的上下文(context)保持一致。这些环境可以是人们进行商业交流或是雇佣的公共场所、战斗场所、魔法森林,也可是一个废弃的太空站。
SafeSandbox基类管理环境的活动范围,并且在受限环境内部和服务端框架的其他部分之间提供了一个坚实的壁垒。它还把服务端框架提供的服务聚合起来,其表现类似于Façade设计模式[Gamma95],但区别在于是从Façade派生而不是把功能委派给它。这些服务包括在沙盘中分发游戏事件、在游戏世界中访问对象以及其他的实用功能号。
这个类的标准构造函数接受一个对游戏世界(world)对象的引用,通过这个引用,玩家可以访问游戏世界中的游戏对象。它还会初始化EventManager类的一个实例,服务端框架通过这个类来分发游戏事件。
class SafeSandbox:
def __init__(self,world):
self.__world = world # access to the game world
self.__eventManager = eventmanager.EventManager()
这个类还声明了_FrameworkInit()方法作为创建后的初始化钩子。如果派生类实现了一个Init()方法,_FrameworkInit会调用它。这个技术可以确保基类的初始化函数总是被框架所调用,并且不需要派生类调用基类中Init()函数的实现,因为派生类的实现者很容易会忘记这一步。
def _FrameworkInit(self):
# do post-creation SandBox init stuff here
# ...
# let derived class init if it likes
if hasattr(self,'Init'):
self.Init()
游戏开发人员希望在SafeSandbox内部只能处理游戏事件而不能发出事件。把一个EventManager实例作为属性保存在SafeSandbox内部,并且覆盖内建方法_getattr_()就可以控制对它的访问。在此,下面的例子通过把EventManager所实现方法的引用赋给类中的同名属性来模拟对RegisterHandler()和UnregisterHandler()的实现。
def __getattr__(self,name):
# Posting of game events is not allowed within the
# sandbox,so we only expose registration and
# unregistration methods.
if name == 'RegisterHandler':
return self.__eventManager.RegisterHandler
elif name == 'UnRegisterHandler':
return self.__eventManager.UnRegisterHandler
else:
raise AttributeError(name)
某些游戏机制需要在SafeSandbox中访问游戏对象,譬如说游戏世界中的玩家角色和物品。然而,受限环境并不需要使用这些对象的所有特性。把它们包装在一个BastionClass代理中返回来,就可以对其进行保护。
def GetGameObject(self,obId):
# Return a protected game object from the world
# region,based on its object id.
gameobject = self.__world.GetGameObject(obId)
return Bastion.Bastion(gameobject)
因为SafeSandbox基类引入了那些游戏开发人员希望在受限环境中不能使用的服务端模块,所以派生类不能像下面的代码那样直接引入SafeSandbox模块。
import safesandbox # exposes modules imported by safesandbox
class MySandbox(safesandbox.SafeSandbox):
# implementation
# ...
相反地,必须在通过受限环境引入派生类所在的模块之后,在运行时建立继承关系。这在Python中很简单,因为一个特定类的基类集合是由类对象(class object)的元组表示的,任何时间都可以对这个元组进行修改。在服务端启动时,_InitSandboxClasses()函数创建了一个由SafeSandbox派生类类对象组成的词典,CreateSafeSandbox工厂函数会使用这个词典在运行时创建实例。
# a global instance of our restricted environment
g_re = sandboxrexec.SandboxRExec()
# a global dictionary of protected sandbox classes
g_classDict = {}
def _InitSandboxClasses(sandboxModules):
for module in sandboxModules:
# expand the row tuple into its elements
moduleName,className = module
# import the module via the restricted environment
try:
module = g_re.r_import(moduleName)
except ImportError:
print 'Cannot import %s.' % (moduleName,)
continue
# get the class object from the sandbox module
safeSandboxClass = getattr(module,className)
# disallow implementing __init__() in derived classes
assert not hasattr(safeSandboxClass,'__init__'),\
'%s must not define __init__()' % (safeSandboxClass,)
# Add the SafeSandbox class into the list of bases,
# creating inheritance without the derived class
# needing to import this module.
bases = safeSandboxClass.__bases__
safeSandboxClass.__bases__ = bases + (SafeSandbox,)
# store the sandbox class object indexed by its name
g_classDict[className] = safeSandboxClass
# end for
注意,派生类不可以实现自己的__init__()构造方法。这是因为游戏开发人员并不想为派生类是否实现了具有正确参数的构造方法或者是否调用了基类的实现而操心。正如前面所描述的,所有派生类的初始化由init()方法完成。
最后,工厂函数在创建SafeSandbox的派生类时,会在_InitSandboxClasses()创建的词典中查找它的类名。在本节的例子中,工厂函数还调用了_FrameworkInit()钩子。
def CreateSafeSandbox(world,className):
# Factory function that returns an instance of the derived
# class of SafeSandbox,as indicated by the className. The
# world parameter is an object containing information
# about the physical game world and the objects it contains.
try:
s = g_classDict[className](world)
s._FrameworkInit() # let derived classes init
return s
except KeyError:
print 'No sandbox class for id [ %s ]' % (className,)
return None
在一个或多个玩家进入这样的环境时,游戏服务端底层架构(infrastructure)会调用CreateSandbox()工厂函数来创建一个相应的SafeSandbox派生类实例。每个派生类都依赖于它所处的环境,它所实现的方法必须按照这个区域的规则对环境中所产生的事件进行处理。可能某个公共场所是禁止战斗的,因此处理程序要能够检测攻击他人的企图并且给予其严厉的惩罚。例如在一个废弃的空间站中,一个发生泄漏的密封舱可能会爆炸也可能会被修复,这完全取决于玩家或其他游戏对象所产生的事件。
2.3.4 在安全沙盘中编写游戏代码
在受限环境中编写游戏代码很简单,程序员只需要为每一个沙盘编写一个Python类就可以了。这个框架不需要特殊的初始化,甚至不必从SafeSandbox基类直接继承。
以下是一个沙盘的例子,它实现的环境中包含了一个会对玩家造成伤害的陷阱。假设在地图的某个地方有一个触发装置,用来检测是否有玩家进入某个特定区域。当玩家进入时,警报器会被激活并且鸣叫5秒,玩家有30秒时间找到地图中某处的复位按钮。如果没有找到,就会有炸弹在他所处的位置爆炸。
首先,从游戏中引入需要的模块。这时,只能引入合法的模块。
# all our imported modules are legal
import sandboxmodules.alarm # an alarm that alerts others
import sandboxmodules.bomb # a bomb class (deadly!)
import events # event ids to register for
接着,声明SafeSandbox的派生类DangerousArea(危险区域)。然后,实现init()方法来创建场景中的对象,并且注册由派生类实现的事件处理程序。这个方法是由_FrameworkInit()调用的。
class DangerousArea:
def Init(self):
# Do our own initialization and register event handlers
# upon start-up here. Called by SafeSandbox.
# alarm goes off every 5 seconds when triggered
self.alarm = alarm.Alarm(interval=5)
self.alarm.Arm()
# bomb explodes with a radius of 10 meters
self.bomb = bomb.Bomb(radius=30,damage=100)
# register handlers with the SafeSandbox base class
self.RegisterHandler(events.TRIGGER_ACTIVATED,\
self.OnTrigger)
self.RegisterHandler(events.ALARM_ALERT,\
self.OnAlert)
self.RegisterHandler(events.BUTTON_RESET,\
self.OnDisarm)
# maximum number of pulses the alarm can fire
# before the bomb goes off (5 sec * 6 pulses = 30 sec)
self.maxAlarmPulses = 6
大部分功能是在事件处理程序中完成的。发挥一下想象力,当玩家走过一个压感盘(plate)或是被一个传感器检测到的时候,会发出一个TRIGGER_ACTIVATED(激活触发器)事件,这时服务端框架会调用OnTrigger()方法。
def OnTrigger(self,player):
# Handle the trigger event. The activator is
# the player who tripped the trigger.
self.alarm.Alert(player) # announce who set off the alarm
当发出ALARM_ALERT(警报)事件时,会调用OnAlert()处理程序。
def OnAlert(self,player,pulsecount):
# Called once for each pulse of the alarm.
# The pulsecount is how many times the
# alert pulse has occurred.
if pulsecount < self.maxAlarmPulses:
# boom... right at players location
self.bomb.explode(player.GetLocation())
如果玩家找到警报器并且关闭了它,就会发出BUTTON_RESET(复位按钮)事件,这会调用OnDisarm()函数。
def OnDisarm(self):
# We stop the alarms pulsing when
# the reset button is pressed.
self.alarm.Disarm() # whew!
1.违反限制
游戏代码中没有对受限环境的直接引用。这是因为所有这些都由沙盘框架自动处理了。然而,如果可以按照下面的处理方法试图引入一个受限的模块。
import database # let’s query the DB!
当模块被引入时,可以看到这个异常。
ImportError: Restricted module: [ database ]
这是因为database(数据库)模块没有包括在SandboxRExec类的ok_server_module列表所列出的允许引入的模块中。
同样,如果工作人员想通过下面的代码把被陷阱抓住的玩家的名字保存到一个文件中。
f = open(‘victims.dat’,‘a’) # append to existing data
f.write(player.GetName())
f.close()
下面的异常会提醒工作人员有人已经违反了规则。
IOError: No access to filesystem is allowed.
最后,如果在这个例子中试图用下面的代码访问player(玩家)对象的一个受限成员,譬如说直接减少他的生命值而不是用炸弹来造成这个伤害。
player.health = player.health - 100
游戏程序员就可以立即从响应中得知他们犯下的错误。
AttributeError: health
作为游戏脚本编写者,他们不必担心什么能做什么不能做,他们将在跨过边界时知道这些。
2.当安全沙盘不安全的时候
本文所描写的技术并不能阻止不良程序员的恶意代码。沙盘中所有的代码是想帮助那些乐于为游戏作出贡献的人实现预期的行为,而不必受服务端框架的结构性细节的干扰。这个系统的目标用户是那些富有创造性的人,他们具有一定的程序或脚本编写技能,并且致力于为玩家创建有趣而引人入胜的游戏体验。
任何刻意地试图突破这个系统强加控制的人很可能找到另一个方法,只要他具有足够的时间、技能和动力。因此,本文不推荐在客户端代码中使用这种方法来防止黑客。
2.3.5 总结
一个MMP开发小组中最好的游戏设计员往往不是最好的程序员。开发小组雇佣他们是因为他们具有想象力、创造力和幽默感,而不是因为他们具有编写结构化代码或是设计关系数据库模型的能力。不过,在实现游戏规则和关卡的代码时,程序设计和游戏设计的规则会交织在一起。这时最好的选择就是由设计人员亲自实现自己的想法。
然而,在这种情况下也不能忘记软件开发是一项工程。MMP开发团队往往希望可以按照某种特定的编程惯例来编写游戏代码。使用安全沙盘方法就可以通过创建一个环境来强制这些惯例的实施。这不仅可以让设计人员把工作重点放在设计上,还可以帮助程序员确保服务端代码的完整性,并且有助于减少因为使用没有预料到的方法而引起大量错误。
2.3.6 参考文献
[Gamma95] Gamma,Erich,Design Patterns: Elements of Reusable Object-Oriented Software,Addison-Wesley,January 1995.
[Kohlbrenner99] Kohlbrenner,Eric,et. al.,“Introduction to Virtual Machines,” http://cne.gmu.edu/itcore/virtualmachine/index.htm,1999.
[Ousterhout98] Ousterhout,John K.,“Scripting: Higher Level Programming for the 21st Century,” http://home.pacbell.net/ouster/scripting.html.
[Python01] van Rossum,Guido,“Comparing Python to Other Languages,” http://www.python.org/doc/essays/comparisons.html.
[Python02] van Rossum,Guido,“rexec — Restricted execution framework,” Python Library Reference,Fred L. Drake,Jr.,ed.,http://www.python.org/doc/current/lib/ module-rexec.html,April 2002.
[Python03] van Rossum,Guido,“sys — System-specific parameters and functions,” Python Library Reference,Fred L. Drake,Jr.,ed.,http://www.python.org/doc/ current/lib module-sys.html,April 2002.
[Sweeney98] Sweeney,Tim,“UnrealScript Language
Reference,” http://unreal. epicgames.
com/ UnrealScript.htm,December 1998.






