写点什么

跟老齐学 Python 之编写模块

  • 2016-04-17
  • 本文字数:5730 字

    阅读完需:约 19 分钟

编者按:InfoQ 开设栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注。本文节选自齐伟著《跟老齐学Python 从入门到精通》中的章节“模块之编写模块”,介绍Python 如何编写自己的模块。

随着对Python 学习的深入,其优点日渐突出,让读者也感觉到Python 的强大了,强大感觉之一就是“模块自信”,因为Python 不仅有自带的模块(称之为标准库),还有海量的第三方模块,并且很多开发者还在不断贡献自己开发的新模块,正是有了这么强大的“模块自信”,Python 才被很多人钟爱。并且这种方式也正在不断被其他更多语言所借鉴,几乎成为普世行为了(不知道Python 是不是首倡者)。

“模块自信”的本质是:开放。

Python 不是一个封闭的体系,而是一个开放系统。开放系统的最大好处就是避免了“熵增”。

熵的概念是由德国物理学家克劳修斯于 1865 年所提出,是一种测量在动力学方面不能做功的能量总数,也就是当总体的熵增加,其做功能力也下降,熵的量度正是能量退化的指标。

熵亦被用于计算一个系统中的失序现象,也就是计算该系统混乱的程度。

根据熵的统计学定义,热力学第二定律说明一个孤立系统倾向于增加混乱程度。换句话说就是对于封闭系统而言,会越来越趋向于无序化。反过来,开放系统则能避免无序化。

编写模块

想必读者已经熟悉了 import 语句,曾经有这样一个例子:

复制代码
>>> import math
>>> math.pow(3,2)
9.0

这里的 math(是 Python 标准库之一,在本章,我们要逐渐理解模块、库之类的术语。)就是一个模块,用 import 引入这个模块,然后可以使用模块里面的函数,比如 pow() 函数。显然,这里是不需要自己动手写具体函数的,我们的任务就是拿过来使用。这就是模块的好处:拿过来就用,不用自己重写。

1. 模块是程序

“模块是程序”一语道破了模块的本质,它就是一个扩展名为.py 的 Python 程序。

我们能够在应该使用它的时候将它引用过来,节省精力,不需要重写雷同的代码。

但是,如果我自己写一个.py 文件,是不是就能作为模块 import 过来呢?还不那么简单。必须得让 Python 解释器能够找到你写的模块。比如,在某个目录中,我写了这样一个文件:

复制代码
#!/usr/bin/env python
# coding=utf-8
lang = "python"

并把它命名为 pm.py,那么这个文件就可以作为一个模块被引入。不过由于这个模块是我自己写的,Python 解释器并不知道,得先告诉它我写了这样一个文件。

复制代码
>>> import sys
>>> sys.path.append("~/Documents/VBS/StartLearningPython/2code/pm.py")

用这种方式告诉 Python 解释器,我写的那个文件在哪里。在这个方法中,也用了模块 import sys,不过由于 sys 是 Python 标准库之一,所以不用特别告诉 Python 解释器其位置。

上面那个一长串的地址是 Ubuntu 系统的地址格式,如果读者使用的是 Windows 系统,请写你所保存的文件路径。

复制代码
>>> import pm
>>> pm.lang
'python'

在 pm.py 文件中有一个赋值语句,即 lang = “python”,现在将 pm.py 作为模块引入(注意作为模块引入的时候不带扩展名),就可以通过“模块名字”+“.”+“属性或方法名称”来访问 pm.py 中的东西。当然,如果要访问不存在的属性,肯定是要报错的。

复制代码
>>> pm.xx
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'xx'

请读者回到 pm.py 文件的存储目录,查看一下是不是多了一个扩展名是.pyc 的文件?

解释器,英文是:interpreter,在 Python 中,它的作用就是将.py 的文件转化为.pyc 文件,而.pyc 文件是由字节码(bytecode)构成的,然后计算机执行.pyc 文件。

很多人喜欢将这个世界简化再简化,比如编程语言就分为解释型和编译型,不但如此,还将两种类型的语言分别贴上运行效率高低的标签,解释型的运行速度就慢,编译型的运行速度就快。一般人都把 Python 看成是解释型的,于是就得出它运行速度慢的结论。不少人都因此上当受骗了,认为 Python 不值得学,或者做不了什么“大事”。这就是将本来复杂的、多样化的世界非得划分为“黑白”的结果,喜欢用“非此即彼”的思维方式考虑问题。

世界是复杂的,“敌人的敌人就是朋友”是幼稚的,“一分为二”是机械的。

如同刚才看到的那个.pyc 文件一样,当 Python 解释器读取了.py 文件,先将它变成由字节码组成的.pyc 文件,然后这个.pyc 文件交给一个叫作 Python 虚拟机的东西去运行(那些号称编译型的语言也是这个流程,不同的是它们先有一个明显的编译过程,编译好了之后再运行)。如果.py 文件修改了,Python 解释器会重新编译,只是这个编译过程不全显示给你看。

有了.pyc 文件后,每次运行就不需要重新让解释器来编译.py 文件了,除非.py 文件修改了。这样,Python 运行的就是那个编译好了的.pyc 文件。

是否还记得前面写有关程序然后执行时常常要用到 if __name__ == “__main__”,那时我们直接用“python filename.py”的格式来运行该文件,此时我们也同样有了.py 文件,不过是作为模块引入的。这就得深入探究一下,同样是.py 文件,它怎么知道是被当作程序执行还是被当作模块引入?

为了便于比较,将 pm.py 文件进行改造。

复制代码
#!/usr/bin/env python
# coding=utf-8
def lang():
return "python"
if __name__ == "__main__":
print lang()

沿用先前的做法:

复制代码
$ python pm.py
python

如果将这个程序作为模块,导入,会是这样的:

复制代码
>>> import sys
>>> sys.path.append("~/Documents/VBS/StarterLearningPython/2code/pm.py")
>>> import pm
>>> pm.lang()
'python'

查看模块属性和方法,可以使用 dir()。

复制代码
>>> dir(pm)
['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'lang']

同样一个.py 文件,可以把它当作程序来执行,也可以将它作为模块引入。

复制代码
>>> __name__
'__main__'
>>> pm.__name__
'pm'

如果要作为程序执行,则 __name__ == “__main__”;如果作为模块引入,则 pm.__name__ == “pm”,即变量 __name__ 的值是模块名称。

用这种方式就可以区分是执行程序还是作为模块引入了。

在一般情况下,如果仅仅是用作模块引入,不必写 if __name__ == “__main__”。

2. 模块的位置

为了让我们自己写的模块能够被 Python 解释器知道,需要用 sys.path.append("~/Documents/ VBS/StarterLearningPython/2code/pm.py")。其实,在 Python 中,所有模块都被加入到了 sys.path 里面。用下面的方法可以看到模块所在位置:

复制代码
>>> import sys
>>> import pprint
>>> pprint.pprint(sys.path)
['',
'/usr/local/lib/python2.7/dist-packages/autopep8-1.1-py2.7.egg',
'/usr/local/lib/python2.7/dist-packages/pep8-1.5.7-py2.7.egg',
'/usr/lib/python2.7',
'/usr/lib/python2.7/plat-i386-linux-gnu',
'/usr/lib/python2.7/lib-tk',
'/usr/lib/python2.7/lib-old',
'/usr/lib/python2.7/lib-dynload',
'/usr/local/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages',
'/usr/lib/python2.7/dist-packages/PILcompat',
'/usr/lib/python2.7/dist-packages/gtk-2.0',
'/usr/lib/python2.7/dist-packages/ubuntu-sso-client',
'~/Documents/VBS/StarterLearningPython/2code/pm.py']

从中也发现了我自己写的那个文件。

凡在上面列表所包括位置内的.py 文件都可以作为模块引入。不妨举个例子,把前面自己编写的 pm.py 文件修改为 pmlib.py,然后复制到’/usr/lib/python2.7/dist-packages 中。(这是以 Ubuntu 为例说明,如果是其他操作系统,读者用类似方法也能找到。)

复制代码
$ sudo cp pm.py /usr/lib/python2.7/dist-packages/pmlib.py
[sudo] password for qw:
$ ls /usr/lib/python2.7/dist-packages/pm*
/usr/lib/python2.7/dist-packages/pmlib.py

文件放到了指定位置。看下面的:

复制代码
>>> import pmlib
>>> pmlib.lang
<function lang at 0xb744372c>
>>> pmlib.lang()
'python'

将模块文件放到指定位置是一种不错的方法,但感觉此法受到了拘束,程序员都喜欢自由,能不能放到别处呢?

当然能,用 sys.path.append() 就是不管把文件放在哪里,都可以把其位置告诉 Python 解释器。虽然这种方法在前面用了,但其实是很不常用的,因为它也有麻烦的地方,比如在交互模式下,如果关闭了,再开启,还得重新告知。

比较常用的方法是设置 PYTHONPATH 环境变量。

环境变量,不同的操作系统设置方法略有差异。读者可以根据自己的操作系统,到网上搜索设置方法。

以 Ubuntu 为例,建立一个 Python 的目录,然后将我自己写的.py 文件放到这里,并设置环境变量。

复制代码
:~$ mkdir python
:~$ cd python
:~/python$ cp ~/Documents/VBS/StarterLearningPython/2code/pm.py mypm.py
:~/python$ ls
mypm.py

然后将这个目录~/python,即 /home/qw/python 设置环境变量。

vim /etc/profile要用 root 权限,在打开的文件最后增加 export PATH = /home/qw/python:$PATH,然后保存退出即可。

注意,我是在~/python 目录下输入 Python,然后进入到交互模式:

复制代码
:~$ cd python
:~/python$ python
>>> import mypm
>>> mypm.lang()
'python'

如此,就完成了告知过程。

3. __all__ 在模块中的作用

上面的模块虽然比较简单,但是已经显示了编写模块,以及在程序中导入模块的基本方式。在实践中,所编写的模块也许更复杂一点,比如,有这么一个模块,其文件命名为 pp.py

复制代码
# /usr/bin/env python
# coding:utf-8
public_variable = "Hello, I am a public variable."
_private_variable = "Hi, I am a private variable."
def public_teacher():
print "I am a public teacher, I am from JP."
def _private_teacher():
print "I am a private teacher, I am from CN."

接下来就是熟悉的操作了,进入到交互模式中。pp.py 这个文件就是一个模块,该模块中包含了变量和函数。

复制代码
>>> import sys
>>> sys.path.append("~/Documents/StarterLearningPython/2code/pp.py")
>>> import pp
>>> from pp import *
>>> public_variable
'Hello, I am a public variable.'
>>> _private_variable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_private_variable' is not defined

变量 public_variable 能够被使用,但是另外一个变量 _private_variable 不能被调用,先观察一下两者的区别,后者是以单下画线开头的,这样的是私有变量。而 from pp import * 的含义是“希望能访问模块(pp)中有权限访问的全部名称”,那些被视为私有的变量或者函数或者类,当然就没有权限被访问了。

再如:

复制代码
>>> public_teacher()
I am a public teacher, I am from JP.
>>> _private_teacher()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_private_teacher' is not defined

这不是绝对的,但如果要访问具有私有性质的东西,可以这样做。

复制代码
>>> import pp
>>> pp._private_teacher()
I am a private teacher, I am from CN.
>>> pp._private_variable
'Hi, I am a private variable.'

下面再对 pp.py 文件进行改写,增加一些东西。

复制代码
# /usr/bin/env python
# coding:utf-8
__all__ = ['_private_variable', 'public_teacher']
public_variable = "Hello, I am a public variable."
_private_variable = "Hi, I am a private variable."
def public_teacher():
print "I am a public teacher, I am from JP."
def _private_teacher():
print "I am a private teacher, I am from CN."

在修改之后的 pp.py 中,增加了 __all__ 变量以及相应的值,在列表中包含了一个私有变量的名字和一个函数的名字。这是在告诉引用本模块的解释器,这两个东西是有权限被访问的,而且只有这两个东西。

复制代码
>>> import sys
>>> sys.path.append("~/Documents/StarterLearningPython/2code/pp.py")
>>> from pp import *
>>> _private_variable
'Hi, I am a private variable.'

果然,曾经不能被访问的私有变量,现在能够访问了。

复制代码
>>> public_variable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'public_variable' is not defined

因为这个变量没有在 __all__ 的值中,虽然以前曾经被访问到过,但是现在就不行了。

复制代码
>>> public_teacher()
I am a public teacher, I am from JP.
>>> _private_teacher()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name '_private_teacher' is not defined

这只不过是再次说明前面的结论罢了。当然,如果以 import pp 引入模块,再用 pp._private_teacher 的方式是一样有效的。

4. 包和库

顾名思义,包和库都是比“模块”大的。一般来讲,一个“包”里面会有多个模块,当然,“库”是一个更大的概念了,比如 Python 标准库中的每个库都有好多个包,每个包都有若干个模块。

一个包由多个模块组成,即有多个.py 的文件,那么这个所谓的“包”就是我们熟悉的一个目录罢了。现在需要解决如何引用某个目录中的模块问题。解决方法就是在该目录中放一个 __init__.py 文件。__init__.py 是一个空文件,将它放在某个目录中,就可以将该目录中的其他.py 文件作为模块被引用。

例如,建立一个目录,名曰:package_qi,里面依次放了 pm.py 和 pp.py 两个文件,然后建立一个空文件 __init__.py

接下来,需要导入这个包(package_qi)中的模块。

下面这种方法很清晰明了。

复制代码
>>> import package_qi.pm
>>> package_qi.pm.lang()
'python'

下面这种方法,貌似简短,但如果多了,恐怕难以分辨。

复制代码
>>> from package_qi import pm
>>> pm.lang()
'python'

在后续制作网站的实战中,还会经常用到这种方式,届时会了解更多。请保持兴趣继续阅读,不要半途而废,不然疑惑得不到解决,好东西就看不到了。

书籍介绍:

本书是面向编程零基础读者的 Python 入门教程,内容涵盖了 Python 的基础知识和初步应用。以比较轻快的风格,向零基础的学习者介绍一门时下比较流行、并且用途比较广泛的编程语言,所以,本书读起来不晦涩,并且在其中穿插了很多貌似与 Python 编程无关,但与学习者未来程序员职业生涯有关的内容。 本书特别强调了学习和使用 Python 的基本方法,学习一种高级语言,掌握其各种规则是必要的,但学会“自省”方法更重要,这也是本书所试图达到的“授人以鱼不如授人以渔”的目的。

2016-04-17 21:0518352

评论

发布
暂无评论
发现更多内容

零数科技入选毕马威中国“2021领先金融科技50企业”

科技热闻

【Node Weekly #417】你需要了解的Node.js内存限制

道道里

前端 Node

通过 Amazon CloudWatch 配合 Amazon ElastiCache for Redis 遵循监控最佳实践

亚马逊云科技 (Amazon Web Services)

计算

Hoo虎符研究院 | 币圈后浪——Osmosis一种高级AMM协议

区块链前沿News

Hoo 虎符交易所 虎符研究院

共建龙蜥社区,支撑商业建设

OpenAnolis小助手

Linux 开源 开发者

武汉智慧城市建设新名片 一城一云打造数字经济新引擎

InfoQ_967a83c6d0d7

如何构建智能湖仓架构?亚马逊工程师的代码实践来了

亚马逊云科技 (Amazon Web Services)

计算

🍃【Spring专题】「技术原理」为大家介绍一下Spring中的Ant路径匹配工具组件AntPathMatcher

洛神灬殇

spring 1月月更 SpringFramework AntPathMatcher

MASA Framework - EventBus设计

MASA技术团队

C# .net 微软 框架 Framework

详解数据湖:概念、特征与架构

五分钟学大数据

数据湖 1月月更

4种Spring Boot 实现通用 Auth 认证方式

华为云开发者联盟

拦截器 spring-boot Auth 传统AOP 参数解析器

面试官: Flink双流JOIN了解吗? 简单说说其实现原理

华为云开发者联盟

sql flink join 双流join 数据库SQL

【Frontend Focus #532】前端性能优化

道道里

前端 性能 浏览器

MobTech观察 | CSDN:企业数字化转型如何提升段位?杨冠军畅谈企业数字化前世今生

MobTech袤博科技

数据分析 数字化转型 数据治理 企业 数据可视化

利用 Amazon Batch 来为容器化负载调用海量云端算力

亚马逊云科技 (Amazon Web Services)

计算

Go Error 嵌套到底是怎么实现的?

AlwaysBeta

Go 源码 源码阅读 Go 语言 源码学习

喜讯!Apache APISIX Committer 张晋涛当选「中国开源先锋 33 人」

API7.ai 技术团队

云原生 微服务网关 APISIX 网关

构建面向异构算力的边缘计算云平台

火山引擎边缘云

gpu 云原生 边缘计算 算力

【等保小知识】等级保护工作是指等保测评吗?意思一样吗?

行云管家

网络安全 等保 等保测评

关于减碳你是否有很多问号?施家碳中和咨询服务来了!

ToB行业头条

产业协同,助力数转 | 鲸鲮正式加入中国电信5G产业创新联盟

鲸鲮JingOS

Linux 5G 操作系统 信创 电信

研究完PagerDuty,我发现一款好用的国产告警管理软件

睿象云

DevOps 运维

开源操作系统年度技术会议演讲PPT下载来啦!

鉴释

开源 操作系统

【伙伴故事】一盏智能灯,点亮家庭和工业照明的新未来

华为云开发者联盟

物联网 华为云 AIOT PLC 智能照明

移动数字化平台如何让企业生态协同更高效?

WorkPlus

零数科技入选毕马威中国“2021领先金融科技50企业”

科技热闻

中小型企业过等保困难有哪些?如何解决?

行云管家

网络安全 企业 过等保

做网络安全竟然不了解ATT&CK?这篇文章的介绍详细到令人尖叫

博文视点Broadview

快过年了,用五种不同的JS特效带你看烟花

海拥(haiyong.site)

大前端 js 28天写作 前端特效 1月月更

“云联邦”构建连云成片、无缝混合的一朵云

华为云开发者联盟

混合云 多云 华为云Stack 云联邦 联邦认证

微帧ROI视频智能编码:基于人眼感兴趣区域,实现极致观感体验

微帧Visionular

视频编码

跟老齐学Python之编写模块_Python_齐伟_InfoQ精选文章