3.2 编写自己的模块

如果对常见编程语言有所了解,就会知道,对于C和C++而言,不论代码有多少行,它们都有一个相同的程序执行入口——main函数,而不论main函数处于整体代码的头部还是尾部,也不管它在众多源代码文件中的哪一个里,都有一种“纵有代码千万行,唯我独尊最先行”的感觉。类似地,Java和C#中会有一个包含main方法的主类,作为程序入口。

然而,Python有所不同,它属于脚本语言,会动态地逐行解释并运行。也就是说,它遵循的逻辑是“先来先到先服务”,即先来的代码先解释、先执行,并没有统一的执行入口。

通过前面的讨论可知,一个Python源文件除了可以被直接解释运行,还可以作为模块(Module)被另外一个Python文档导入执行。不管直接运行,还是被导入执行,顶层的代码都会被执行。而实际上,在作为模块被导入时,可能会有一部分代码,我们不希望它被第三方执行。这时,该怎么办呢?

下面我们用【范例3-1】来说明。假设在一个工程中,我们有两个Python文件。其中一个是parameters.py,在这个模块中,我们定义了某些参数的值。另外一个是calculate.py,该模块需要使用前一个模块中定义的参数。我们先来看看parameters.py的代码。

【范例3-1】自定义模块(parameters.py)

仅就这个模块本身而言,不论是直接在IDE环境点击执行按钮(▶)来执行程序,还是在命令行使用python parameters.py来执行,结果都非常简单,如下所示。

【运行结果】

上述代码自娱自乐是没有问题的,但问题往往出现在彼此协作之时。

现在假设另一个文件calculate.py想实现求解圆形面积的功能,需要用到parameters.py中的参数PI。于是,很自然地,我们想把parameters.py作为第三方模块导入当前程序并为我所用,代码如下所示。

需要注意的是,作为包名,在被第三方程序导入时,不要将Python文件的后缀名“.py”放到导入参数中。当前文件calculate.py的代码如下。

保存calcalute.py(此时需要确保和parameters.py保存在同一个路径下)并执行,得到的运行结果如下。

【运行结果】

从结果中可以看出,圆形面积的确是求解出来了,但在parameters.py中写的测试paraTest()也被执行了,而这并不是我们想要的。有没有办法解决这个问题呢?其实,办法总比问题多!

Python解释器在执行代码时有很多内置变量,__name__就是其中之一,其意义是“模块名”。这个内置变量很神奇,其神奇之处在于,它的值能够“见风使舵”,懂得“内外有别”,即面对不同的对象将呈现出不同的值。

假设当前模块声明了这个内置变量,如果本模块直接执行,那么这个__name__的值就为__main__需要注意的是,__name__左右两侧都是两个下画线,__main__亦同。。如果它被第三方引用(即通过import导入),那么它的值就是这个模块名,即它所在Python文件的文件名(不含扩展名.py)。

有了这个区分,就可以用逻辑判断把不想被第三方模块执行的代码“保护起来”。现在我们来改写parameters.py(修改了第07~08行),如图3-1所示。

图3-1 __name__属性值的内外有别

再次运行calcalute.py程序,结果就如我们所要的,它并没有运行在parameters.py中写的测试函数paraTest()。

【运行结果】

【代码解析】

出现上述结果的原因很简单。对于calcalute.py而言,虽然在第02行导入了parameters模块,但是在calcalute.py文件中,作为第三方模块的parameters,其特有属性__name__的值为parameters,而不是__main__,也就是说,parameters.py文件中的第07~08行,由于逻辑条件为False而不被允许执行。