本节视频涉及到的实例源码,可以在百度网盘中下载。在公众号对话框回复关键字“网盘地址”,获取网盘链接和提取码。
课件01
—
组织你的代码
通常,我们的程序不会只有一个函数,如果功能需求稍微复杂的程序,也不会都写在一个.py文件里面。这就涉及到一个问题,我们该如何组织这些.py文件呢? 正如我们日常工作中将我们的文档通过文件夹分类一样,我们也通过.py文件以及文件夹的方式进行分类组织。一个大的项目可能有数百个.py文件,这种分类手段是有必要的,而且是必需的。 我们将.py文件称作python的模块(module),而将这种文件夹(带__init__.py),称之为python的包(package)。 我们利用模块和包,可以更好地组织我们项目的代码。同时,它们也提供了类似函数那样的代码复用能力。 我们来看python官方手册提供的一个典型案例,下面是一个关于“声音处理”的包目录层次结构: sound/ Top-level package __init__.py Initialize the sound package formats/ Subpackage for file formatconversions __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... effects/ Subpackage for sound effects __init__.py echo.py surround.py reverse.py ... filters/ Subpackage for filters __init__.py equalizer.py vocoder.py karaoke.py ... “声音处理”包含了很多功能,比如对各种声音格式的支持、音效增强、过滤器等等。我们将这些功能进行分层拆分,分别放在不同的.py和package中实现。 这里的sound是一个顶层的package,而formats、filters、effects是它的子package,package支持这样的层层嵌套,和文件夹目录一样。需要注意的是,只有包含了__init__.py的目录才会被认为是一个package,否则它就是一个普通的文件夹。__init__.py可以为空,后面我们会详细介绍它的写法。 这里面的所有.py文件都是模块。模块里面的内容没有要求,可以包含变量、函数、类,也可以为空。 Python解释器本身提供了庞大的标准库,其本质就是模块或者包。我们使用比较多的re、datetime、copy、array、enum、os、sys、io等等都是标准库。 你可以通过查询官方的标准库参考手册: https://docs.python.org/zh-cn/3/library/index.html 后面我们也会重点介绍一些常用的标准库。02
—
模块的三种导入方式
模块和包,是可以被复用的。比如上面的sound包,我们可以在其它的python程序中导入它,并调用它里面的函数、变量或者类。 这一过程,通过Import机制来实现,如下是最简单的方式:# author: Tiger, 关注公众号“跟哥一起学python”,ID:tiger-python# file: ./10/10_1.py# 模块import keywordprint(keyword.kwlist)
我们通过import导入了一个模块keyword,并接下来获取了它的一个变量kwlist打印出来。
Import语句需要写在使用它的代码之前,而通常我们是建议所有的import语句都写在文件的开头。
我们看看keyword.py是什么样的?它非常简单:
# LIB/keyword.py __all__ =["iskeyword", "kwlist"] kwlist = [ 'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield'] iskeyword =frozenset(kwlist).__contains__
第一行,给一个叫__all__的变量进行了赋值。
这个变量有特殊含义,它是给python解释器看的,它表示在import该模块时需要导入的符号列表。
第二行,定义了一个变量kwlist,是一个列表,它里面存了python的所有保留字。
第三行,定义iskeyword是frozenset的一个内建函数__contains__,它判断一个字符串是不是kwlist里面的保留字。
我们还可以通过
from xxx importxxx
的方式导入:
from keyword importkwlistprint(kwlist)
这种导入比前面的要精确,范围更小。这种导入方式适合你明确知道你需要的功能的时候,比如这个例子中,我明确知道我只需要打印kwlist,所以可以指定只导入kwlist。
这种方式在使用时会显得更加简洁,我们只需要使用kwlist即可,而不用像上面那样写keyword.kwlist。
另一个好处是,第一种方式会把这个模块的所有符号一股脑的全部导入进来,而第二种方式则更加精确。当我们需要使用同一个模块中的多个函数或者变量时,我们可以采用第一种方式,因为采用第二种方式的话我们会写多次from xxx import xxx。
无论上面哪种方式,它们都存在命名冲突的风险。比如我们自己的程序中可能已经存在一个变量叫做keyword或者kwlist了。这时候我们需要为我们导入的模块或者函数起一个别名,如下:
import keyword askwmodulefrom keyword importkwlist as kwallprint(kwmodule.kwlist)print(kwall)
我们通过as xxx来起别名,如上kwmodule就是keyword模块的别名,而kwall就是变量kwlist的别名。在下面的使用中,我们就只能使用这个别名。这就解决了命名重复的问题。
第二种导入方式也可以导入模块中所有的符号(变量、函数、类等),需要使用*通配符来表示。它和第一种方式的全部导入,是有区别的。第一种方式导入的符号是keyword,模块中的所有符号都包含在keyword里面,所有调用方式都必须是keyword.xxx的方式。而采用*号通配符,则是将模块中的符号全部导入进来,可以直接调用。 我们可以通过dir(obj)内建函数,打印出某个对象的符号表,如果不带参数,则打印当前模块的符号表。import keywordprint(dir())print(dir(keyword))
输出为:
['__annotations__', '__builtins__', '__cached__','__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__','keyword']
['__all__', '__builtins__', '__cached__', '__doc__','__file__', '__loader__', '__name__', '__package__', '__spec__', 'iskeyword','kwlist']
前面那些__XXX__的大家先不用关注,他们是python对象自带的符号。我们看到,这种import方式,它只导入了keyword。而keyword里面包含了iskeyword和kwlist。
from keyword import *print(dir())
输出为:
['__annotations__', '__builtins__', '__cached__', '__doc__','__file__', '__loader__', '__name__', '__package__', '__spec__', 'iskeyword','kwlist']
这种方式是直接把模块中的符号导入进来了,平铺在当前符号表中。 *号通配符的方式,通常不被推荐使用。一个重要的原因是,如果模块中的符号很多的时候,你很难保证不出现重名的情况,重名导致的覆盖会让你的程序变得很难看懂,而且容易出错。另外,我们的程序中通常会导入多个外部模块,要保证这些模块中的符号相互之间不命名冲突,几乎是不可能的。下一节我们继续讲模块和包。
-end-
有问题请评论留言,欢迎交流!