星期六, 二月 10, 2007

python name declaration order

import logger

class CTreeEx:
def __init__(self, ...):
......
def logError(self, logerr=outlog):
print >> sys.stderr, self.strerr)
if self.errno == 1: logerr.error(self.strerr)
elif self.errno == 127: logerr.critical(self.strerr)

class CTree:
......
def func():
......
raise CTreeEx(...)

def main():
......
ctobj = CTree()
ctobj.func()

if __name__ == '__main__':
outlog = logger.get(program)
usage = "program usage ..."
main()
else:
outlog = logger.get(__name__)
usage = "module usage ..."

sh$ ./ctree.py
Traceback (most recent call last):
File "./ctree.py", line 78, in ?
class CTreeEx:
File "./ctree.py", line 84, in CTreeEx
def logError(self, logerr=outlog):
NameError: name 'outlog' is not defined
这里抛出 NameError 的异常。因为 python 是动态类型变量,所以不可能有 C 语言那样的声明(declare)方式可以在赋值之前进行声明,所以对于名字问题只能通过调整出现的顺序来解决。所以这里应该调整为:
def main():
if __name__ == '__main__':
class CTreeEx:
class CTree:
注意这里 main() 也要提到前面,在 if __name__ == '__main__': 之前,否则又会出现“NameError: name 'main' is not defined”。

虽然有 global 语句,但也不能这样使用:
if __name__ == '__main__':
outlog = logger.get(program)
global outlog

sh$ ./ctree.py
./ctree.py:0: SyntaxWarning: name 'outlog' is assigned to before global declaration
Traceback (most recent call last):
File "./ctree.py", line 78, in ?
class CTreeEx:
File "./ctree.py", line 84, in CTreeEx
def logError(self, logerr=outlog):
NameError: name 'outlog' is not defined
当然这里只是一个 Warning,但这也说明这样是有问题的。要知道 python 的结构性已经很强了。而在最前面使用 global:
global outlog
class CTreeEx:
......
则不会有任何效果,仍然会抛出 NameError 异常。

对这个问题要深入研究一下。假设我在最上面的顺序下这样做:
class CTreeEx:
def __init__(self, ...):
print usage
这样,看上去对 usage 的使用在 outlog 之前了,应该会抛出 'usage' 的 NameError,但实际上却不会! 换一种作法却会:
class CTreeEx:
def __init__(self, ..., test=usage):

NameError: name 'usage' is not defined
所以,可以看到,只有那些定义,或成为赋值的语句会有影响,即 assignment──在 python 中,因为是动态类型,所以所有的名字和对象之间都相当于一个 C 语言中的指针赋值,所以只有赋值,而 class name:/def name(): 都只不过是将一个 class/function 对象赋给一个名字 name,甚至包括 import name 也是将一个 module(文件)赋给一个 name。

那么如果这样呢?
class CTreeEx:
def __init__(self, errno=1, strerr='CTree,Exception'):
test = usage
这样却不会有问题。所以这取决于这个赋值会在何时发生。象这里的 class/def 定义,都是在 module 顶层,是在从命令行调用或 import module 时马上就会执行的部分,包括 def __init__() 也是在生成 class CTreeEx 这个 class 对象时必须的部分,所以会有问题;但 __init__() 内部的东西则是在生成 class instance 对象时才调用的,所以不会有问题。可以看看这样的效果:
class CTreeEx:
test = usage
def __init__(self, ...):
就完全明白了。

没有评论: