问题引出
编写业务代码时经常会出现几个模块间相互调用的情况,本文对具体情形的出现和错误解决做相关分析。
Python模块调用的原则
在一次调用过程中,如果一个Python文件被当作模块调用,则最多只能被调用一次,再次运行到调用语句时会直接跳过。
案例
- Student作为测试类
- 预期过程是模块1作为入口文件执行,先创建student对象,再调用模块2来为student.name赋值,最后输出student.name="小明"
- 由于模块1中需要调用模块2为student.name赋值,而模块2中需要调用模块1来获取student对象,所以这里产生了循环调用
# 测试类
class Student:
name = '无名'
# 模块1
print('模块1被执行')
from text.Student import Student
student = Student()
import text.module2
if __name__ == '__main__':
print('模块1输出: '+ student.name + str(id(student)))
# 模块2
print('模块2被执行')
from text.module1 import student
student.name = '小明'
print('模块2输出: '+ student.name + str(id(student)))
# 执行结果
模块1被执行
模块2被执行
模块1被执行
模块2输出: 小明2661321739960
inlet输出: 无名2661290013472
结果分析
期望的最终结果应该是模块1输出小明,然而实际情况下模块1输出的却是无名,根据其他输出内容我们可以推得以下的执行过程:
- 模块1先作为入口文件被执行,创建student(id:3472)后调用模块2
- 程序进入模块2中尝试调用模块1来获取student对象,由于刚才模块1的是作为入口文件被执行的,所以这次调用成功
- 程序再次进入模块1,创建了第二个student(id:9960),尝试调用模块2时由于不符合只能调用一次的原则直接跳过,全部执行完后程序回到模块2中
- 模块2得到了调用模块1时所创建的student(id:9960),并为其赋值后输出,全部执行完后程序回到模块1中
- 模块1作为入口文件只有student(id:3472)的存在,故最后输出的依然是无名
总结:模块2为调用模块1时新创建的student(id:9960)完成了赋值,而主程序模块1中的student(id:3472)并没有被赋值。
解决方案
- 发生问题的实际原因是模块1在作为入口文件和作为模块被调用时都执行了一次,产生了两个不同的student对象,而解决方案的思考方向是让模块1只执行一次,这样就只有唯一的student存在
- 根据模块只能被调用一次的原理,则可以引入inlet作为单独的入口文件,让模块1和模块2都只作为模块被调用
# inlet
from text.module1 import student
if __name__ == '__main__':
print('模块3输出: '+ student.name + str(id(student)))
# 模块1
print('模块1被执行')
from text.Student import Student
student = Student()
import text.module2
# 模块2
print('模块2被执行')
from text.module1 import student
student.name = '小明'
print('模块2输出: '+ student.name + str(id(student)))
# 执行结果
模块1被执行
模块2被执行
模块2输出: 小明1841515662304
模块3输出: 小明1841515662304
结果分析
模块3成功输出了小明,根据其他输出内容我们可以推得以下的执行过程:
- inlet作为入口文件被执行,调用模块1
- 程序进入模块1中创建了student(2304),调用模块2
- 程序进入模块2中先试图调用模块1,但由于模块1已经作为模块被inlei调用过一次所以直接跳过
- 模块2为已经存在的student(2304)赋值,全部执行完后返回模块1
- 模块1全部执行完后返回inlet
- inlet输出唯一的student(2304)
总结:通过单独设立入口文件从而让发生循环调用的两个模块都只作为模块被调用一次,以此来产生唯一的student对象,保证了输出结果的正确性 (实际上形成了单向的调用过程)。