一、Python模块
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。
使用模块有什么好处?
- 首先,提高了代码的可维护性。
- 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他的模块引用。不要重复造轮子,我们简简单单地使用已经有的模块就好了。
- 使用模块还可以避免类名、函数名和变量名发生冲突。相同名字的类、函数和变量完全可以分别存在不同的模块中。但是也要注意尽量不要与内置函数名(类名)冲突。
二、Package
你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
举个例子,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。
现在,假设我们的abc和xyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany,按照如下目录存放:
1 2 3 4 |
mycompany/ ├── __init__.py ├── abc.py └── xyz.py |
请注意,每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件,也可以有Python代码,因为__init__.py
本身就是一个模块,而它的模块名就是mycompany。
类似的,可以有多级目录,组成多级层次的包结构。比如如下的目录结构:
1 2 3 4 5 6 7 8 9 |
mycompany/ ├── web │ ├── __init__.py │ ├── utils.py │ └── www.py ├── __init__.py ├── abc.py ├── utils.py └── xyz.py |
文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utils和mycompany.web.utils。
自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。mycompany.web也是一个模块,请指出该模块对应的.py文件。
包名通常为全部小写,避免使用下划线。
三、编写模块
Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。
我们以内建的sys模块为例,编写一个hello的模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/env python # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys def test(): args = sys.argv if len(args)==1: print('Hello, world!') elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Too many arguments!') if __name__=='__main__': test() |
第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
后面开始就是真正的代码部分。
你可能注意到了,使用sys模块的第一步,就是导入该模块:
1 |
import sys |
导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python hello.py获得的sys.argv就是[‘hello.py’];
运行python hello.py Michael获得的sys.argv就是[‘hello.py’, ‘Michael]。
最后,注意到这两行代码:
1 2 |
if __name__=='__main__': test() |
当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
我们可以用命令行运行hello.py看看效果:
1 2 3 4 |
$ python hello.py Hello, world! $ python hello.py Michael Hello, Michael! |
如果启动Python交互环境,再导入hello模块:
1 |
>>> import hello |
导入时,没有打印Hello, word!,因为没有执行test()函数。
调用hello.test()
时,才能打印出Hello, word!:
1 2 |
>>> hello.test() Hello, world! |
四、导入机制
要在我们的程序中,使用其它的模块(包、类、函数),就必须先导入对应的模块(包、类、函数)。在Python中,模块(包、类、函数)的导入方式有以下四种。
- import xx.xx
这会将对象(这里的对象指的是包、模块、类或者函数,下同)中的所有内容导入。如果该对象是个模块,那么调用对象内的类、函数或变量时,需要以module.xxx的方式。
比如,被导入的模块module_a:
1 2 3 4 |
# module_a.py def func(): print("this is module A!") |
在main.py中导入module_a:
1 2 3 4 |
# main.py import module_a # 导入模块 module_a.func() # 调用方法 |
- from xx.xx import xx
从某个对象内导入某个指定的部分到当前命名空间中,不会将整个对象导入。这种方式可以节省写长串导入路径的代码,但要小心名字冲突。
在main.py中导入module_a:
1 2 3 4 5 |
# main.py from module_a import func # 导入函数 module_a.func() # 错误的调用方式 func() # 这时需要直接调用func |
- from xx.xx import xx as rename
为了避免命名冲突,在导入的时候,可以给导入的对象重命名。这样,可以在运行时根据当前环境选择最合适的模块。
1 2 3 4 5 6 7 8 9 |
# main.py from module_a import func as f def func(): # main模块内部已经有了func函数 print("this is main module!") func() f() |
Python标准库一般会提供StringIO和cStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快。所以,你会经常看到这样的写法:
1 2 3 4 |
try: import cStringIO as cStringIO except importortError: import cStringIO |
这样就可以优先导入cStringIO,如果有些平台不提供cStringIO,还可以降级使用StringIO。导入cStringIO时,用import … as …指定了别名StringIO,因此,后续代码引用StringIO即可正常工作。
- from xx.xx import *
将对象内的所有内容全部导入。非常容易发生命名冲突,请慎用!
1 2 3 4 5 6 7 8 |
# main.py from module_a import * def func(): print("this is main module!") func() # 从module导入的func被main的func覆盖了 |
对于xx.xx的说明:
由于一个模块可能会被一个包封装起来,而一个包又可能会被另外一个更大的包封装起来,所以我们在导入的时候,需要提供导入对象的绝对路径,也就是“最顶层的包名.次一级包名.(所有级别的包名).模块名.类名.函数名”。类似文件系统的路径名,只是用圆点分隔的。
有时候,模块名就在搜索路径根目录下,那么可以直接 import 模块名,比如Python内置的一些标准模块,os、sys、time等等。
大多数时候,我们不需要直接导入到函数的级别,只需要导入到模块级别或者类的级别,就只需要使用import Django.contrib.auth.models导入models模块,以后使用models.User来引用models模块中的类。
总之,对于xx.xx,你想导入到哪个级别,取决于你的需要,可以灵活调整,没有固定的规则。
import导入模块时会执行三个步骤:
找到模块文件:在指定的路径下搜索模块文件。
编译成字节码:文件导入时就会编译;因此顶层文件.pyc字节码文件在内部使用后会被丢弃只有被导入的文件才会留下.pyc文件。
执行模块的代码来创建其所定义的对象:模块文件中的所有语句会一次执行从头到尾;而此步骤中任何对变量名的赋值运算都会产生所得到的模块文件的属性。
注意:模块只在第一次导入时才会执行如上步骤,后续的导入操作只不过是提取内存中已加载的模块对象,reload()可用于重新记载模块。
五、模块作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等。类似__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名。类似_xxx和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等。
之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:
1 2 3 4 5 6 7 8 9 10 11 |
def _private_1(name): return 'Hello, %s' % name def _private_2(name): return 'Hi, %s' % name def greeting(name): if len(name) > 3: return _private_1(name) else: return _private_2(name) |
我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
六、模块搜索路径
不管你在程序中执行了多少次import,一个模块只会被导入一次。这样可以防止一遍又一遍地导入模块,节省内存和计算资源。那么,当使用import语句的时候,Python解释器是怎样找到对应的文件的呢?
Python根据sys.path的设置,按顺序搜索模块。
1 2 3 4 5 6 7 8 |
# 导入模块; >>> import sys # 搜索模块路径; >>> sys.path # 添加模块搜索路径; >>> sys.path.append('/usr') |
就像Linux系统环境变量中的PATH一样,可以自定义添加或删除某些搜索路径。
默认情况下,模块的搜索顺序是这样的:
- 当前执行脚本所在目录
- Python安装目录
- Python安装目录里的site-packages目录
其实就是“自定义”——>“内置”——>“第三方”模块的查找顺序。任何一步查找到了,就会忽略后面的路径,所以模块的放置位置是有区别的。
比如需要在当前路径下导入父级模块。
1 |
sys.path.append("..") |
七、第三方模块
在Python中,安装第三方模块,是通过setuptools这个工具完成的。Python有两个封装了setuptools的包管理工具:easy_install和pip。目前官方推荐使用pip,让我们来安装一个第三方库——Python Imaging Library,这是Python下非常强大的处理图像的工具库。一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Python Imaging Library的名称叫PIL,因此,安装Python Imaging Library的命令就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# CentOS安装EPEL源; $ yum install epel-release # 安装pip工具; $ yum install python-pip # 使用pip安装包; $ pip install Pillow # pip可用命令; $ pip -help Usage: pip <command> [options] Commands: install Install packages. download Download packages. uninstall Uninstall packages. freeze Output installed packages in requirements format. list List installed packages. show Show information about installed packages. check Verify installed packages have compatible dependencies. search Search PyPI for packages. wheel Build wheels from your requirements. hash Compute hashes of package archives. completion A helper command used for command completion. help Show help for commands. |
八、__future__
模块
由于Python是由社区推动的开源并且免费的开发语言,不受商业公司控制。因此,Python的改进往往比较激进,不兼容的情况时有发生。Python为了确保你能顺利过渡到新版本,特别提供了__future__模块,让你在旧的版本中试验新版本的一些特性。
如从Python 2.7到Python 3.x就有不兼容的一些改动,比如2.x里的字符串用’xxx’表示str,Unicode字符串用u’xxx’表示unicode,而在3.x中,所有字符串都被视为unicode。因此,写u’xxx’和’xxx’是完全一致的,而在2.x中以’xxx’表示的str就必须写成b’xxx’,以此表示“二进制字符串”。
在Python 2.x中,对于除法有两种情况,如果是整数相除,结果仍是整数,余数会被扔掉,这种除法叫“地板除”。python 2.x中要做到精确除必须把其中一个数变成浮点数,如下:
1 2 3 4 |
>>> 10 / 3 3 >>> float(10) / float(3) 3.3333333333333335 |
python 3.x中所有的除法都使用精确除法,地板除需要使用//实现。如果你想在Python 2.7的代码中直接使用Python 3.x的除法,可以通过__future__
模块的division实现,如下:
1 2 3 |
>>> from __future__ import division >>> print (10/3) 3.33333333333 |
完结。。。清晰版请看廖雪峰的官网,我这里只是拷贝他的,做一下个人记录,加深一下印象,另外有新东西便于加入。