从Scratch到Python 21 更复杂的程序结构
2023-06-06 11:30:55
我们已经学习了模块,也学习了面向对象编程,以及如何使用第三方库,可以编写规模更大、逻辑更复杂一些的程序了。要编写这类程序,我们必须先掌握一些重要概念,分别是包、命名空间与作用域。
一、包
什么是包呢?包实际上就是一个文件夹。现实中我们文件资料太多,不容易分类查找的时候,我们会用不同的文件夹把它们分类整理;使用计算机时产生的文件,我们也会放到不同的文件夹,比如音乐文件、文档文件、视频文件等;对于编程来说,如果我们要实现的代码很多,我们首先会把它分成模块来编写,每个模块就是一个.py文件。那如果模块多起来怎么办呢?最直接的就是建立不同的文件夹,把不同类型的模块放到各自的文件夹中去,这种存储模块的文件夹就是包。 我们来举个例子,现在假如我们要编写一个学校管理程序,里面有教师相关的功能、学生相关的功能、图书相关的功能,每一个都有不同的模块,该怎么组织我们的代码呢? 我们来建立一个稍微复杂点的项目结构。打开海龟编辑器,点击左上角的项目文件夹按钮:




def fun_a():
print('This is teacher.mod1.fun_a!')
此时界面如下:

def fun_b():
print('This is teacher.mod2.fun_b!')

from teacher import mod1,mod2
mod1.fun_a()
mod2.fun_b()
这时运行程序,会发现正确调用了包中的代码:

from . import mod1
from . import mod2
然后你就可以修改”我的文件.py“中的代码,直接导入teacher了:
import teacher
teacher.mod1.fun_a()
teacher.mod2.fun_b()
为什么会这样呢?还记得我们学面向对象时创建类的实例时,它会自动调用init()方法吗?同样的,init.py这个文件中的内容会在你导入包的时候自动运行,里面的from . import mod1意思是从当前目录导入mod1模块。这样,init.py就成了teacher包的一个目录,当你导入包的时候,包的每一个模块也被导入了,你可以在主程序中使用它们,非常方便。
如果teacher文件夹下面还有子文件夹,我们同样应该为它们建立init.py作为目录,当你导入teacher时,会通过递归的方式把每一层的模块都导入进来供主程序使用。
到这里,包的知识我们就讲完了,唯一需要注意的就是这个init.py,它是方便我们使用包的一个重要工具。
二、全局变量与局部变量
看下面这段程序:def test_func(a,b):
c = a * b
return c
print(c)
你觉得这段简单的程序有问题吗?如果你没有把握,可以运行一下。会发现程序报错了:

c = 1000
def test_func(a,b):
c = a * b
return c
d = test_func(2,3)
print('d=',d)
print('c=',c)
先不要看分析,你觉得程序输出的变量d和变量c的值相同吗?为什么?
答案是不同,尽管你看到我们调用test_func(2,3)的时候,将一个变量“c”赋值为2*3,同时这个结果也返回给了d,但程序的输出是这样的:

global关键字,告诉Python我要用的就是全局变量,不是一个新的局部变量:
c = 1000
def test_func(a,b):
global c
c = a * b
return c
d = test_func(2,3)
print('d=',d)
print('c=',c)
再次运行,这次的变量c和变量d值相同了,都是6。

c = [1,2]
def test_func(a,b):
c.append(a+b)
return c
d = test_func(2,3)
print('d=',d)
print('c=',c)
在上面这段程序中,c是一个列表型变量,初始状态下它有1、2两个元素,在函数内部我们直接将a+b的值作为一个新元素添加到c中(在函数内部并没有声明c),结果函数居然没有出错,还修改了全局变量c的值:

c = [1,2]
def test_func(a,b):
c = []
c.append(a+b)
return c
d = test_func(2,3)
print('d=',d)
print('c=',c)

- 简单数据类型的局部变量只能在函数内部创建和使用
- 简单数据类型的局部变量用global关键字声明后可以当作全局变量使用
- 组合数据类型的全局变量(如列表),在函数内部没有创建的情况下,可以修改全局变量
- 组合数据类型的全局变量(如列表),在函数创建同名变量的情况下,函数只修改局部变量,不影响全局列表的值
三、命名空间及作用域
从全局变量和局部变量的使用规则我们可以看出,变量名字重复是挺麻烦的。那么如果一个程序由多个模块组成,每个模块又是不同的程序员开发的,万一他们用的变量名字重复了怎么办呢?会不会出错? 这一点你可以放心,因为Python中有“命名空间”的概念。命名空间是名称到对象的对应关系,一个模块里所有的名称属于同一个命名空间,所以命名不能重复;但模块1和模块2就是两个不同的命名空间,假如两个模块中都有同一个变量a,也不会相互影响。在现实中有一个对应的例子——假如你们班有位同学叫李磊,隔壁班也有个同学叫李磊,大家在各自的班级不会混淆;如果校长想找李磊,他只要带上命名空间就行,比如一二班的李磊,或者一三班的李磊,也能分得清。 Python中有下面三类命名空间:- 内置名称:包括Python内置的函数和异常名称等,如str,print,NameError;
- 全局名称:包括在程序中使用的名称和各种导入模块的名称。这个命名空间在程序创建时被创建,程序运行结束时终止;
- 局部名称:包含一个函数中定义的名称,包括了函数的参数和局部变量的名称等。
# 调用内部函数print
print('hello')
# 定义一个与内部函数print重名的函数
def print(a):
# pass 代表“什么也不做”
pass
# 再次调用print
print('world')
在这个例子里我们定义了一个与内置函数print()重名的函数,当你在它后面调用print('world')的时候,它调用的就是全局名称,而不是Python内置名称,所以调用了我们自己的print()函数,这个函数什么也不做(只有一行pass语句),所以程序只输出了’hello',没有'world'。

微信扫码关注
抖音扫码关注