1.2.5. 重用代码:脚本和模块

现在,我们在解释器中键入了所有的指令。对于更长的指令集,我们需要改变一下,在文本文件中编写代码(使用文本编辑器),我们将称它们为脚本模块使用你最喜欢的文本编辑器(只要它为Python提供语法高亮),或者使用你可能正在使用的科学计算Python套件自带的编辑器(例如,Python(x,y)的Scite)。

1.2.5.1. 脚本

让我们先写一个脚本,它是一个具有指令序列的文件,这些指令在每次调用该脚本时都会执行。指令可以是从解释器复制和粘贴过来的(但注意遵守缩进规则!)。

Python文件的扩展名为.py将以下行写入或复制并粘贴到名为test.py的文件中

message = "Hello how are you?"
for word in message.split():
print word

让我们现在以交互方式执行脚本,也就是在Ipython解释器中。这可能是在科学计算中脚本最常见的使用方式。

注意

在Ipython中,执行脚本的语法是%run script.py例如,

In [1]: %run test.py
Hello
how
are
you?
In [2]: message
Out[2]: 'Hello how are you?'

脚本已执行。此外,脚本中定义的变量(例如message)现在可在解释器的命名空间中使用。

其他解释器也可以执行脚本(例如,在纯Python解释器中的execfile)。

也可以通过在shell终端(Linux/Mac控制台或Windows cmd控制台)中执行脚本,将此脚本作为独立程序执行。例如,如果我们在与test.py文件相同的目录中,我们可以在控制台中执行:

$ python test.py
Hello
how
are
you?

独立脚本也可以接收命令行参数

file.py中:

import sys
print sys.argv
$ python file.py test arguments
['file.py', 'test', 'arguments']

警告

不要自己实现选项解析。使用诸如optparseargparsedocopt等模块。

1.2.5.2. 从模块导入对象

In [1]: import os
In [2]: os
Out[2]: <module 'os' from '/usr/lib/python2.6/os.pyc'>
In [3]: os.listdir('.')
Out[3]:
['conf.py',
'basic_types.rst',
'control_flow.rst',
'functions.rst',
'python_language.rst',
'reusing.rst',
'file_io.rst',
'exceptions.rst',
'workflow.rst',
'index.rst']

还有:

In [4]: from os import listdir

导入shorthands:

In [5]: import numpy as np

警告

from os import *

这称为星号导入请小心使用

  • 使代码更难阅读和理解:符号在哪里来自?
  • 使它不可能通过上下文和名称猜测功能(提示:os.name是操作系统的名称),并有效利用制表符补全。
  • 限制可以使用的变量名:os.name可能覆盖name,反之亦然。
  • 在模块之间创建可能的名称冲突。
  • 使代码不可能静态检查未定义的符号。

因此,模块是一种以分层方式组织代码的好方法。实际上,我们将使用的所有科学计算工具都是模块:

>>> import numpy as np # data arrays
>>> np.linspace(0, 10, 6)
array([ 0., 2., 4., 6., 8., 10.])
>>> import scipy # scientific computing

在Python(x,y)中,Ipython(x,y)在启动时执行以下导入:

>>> import numpy
>>> import numpy as np
>>> from pylab import *
>>> import scipy

并且不必重新导入这些模块。

1.2.5.3. 创建模块

如果我们想要编写更大和更好的组织程序(与简单脚本相比),其中定义了一些对象(变量、函数、类),并且我们想要重用多次,我们必须创建自己的模块

让我们创建一个模块demo,包含在文件demo.py中:

"A demo module."
def print_b():
"Prints b."
print 'b'
def print_a():
"Prints a."
print 'a'
c = 2
d = 2

在此文件中,我们定义了两个函数print_aprint_b假设我们要从解释器调用print_a函数。我们可以以脚本的形式执行文件,但由于我们只想访问print_a函数,我们更愿意导入它为模块语法如下。

In [1]: import demo
In [2]: demo.print_a()
a
In [3]: demo.print_b()
b

导入模块可使用module.object语法访问其对象。不要忘记将模块的名称放在对象的名称之前,否则Python将无法识别指令。

内省

In [4]: demo?
Type: module
Base Class: <type 'module'>
String Form: <module 'demo' from 'demo.py'>
Namespace: Interactive
File: /home/varoquau/Projects/Python_talks/scipy_2009_tutorial/source/demo.py
Docstring:
A demo module.
In [5]: who
demo
In [6]: whos
Variable Type Data/Info
------------------------------
demo module <module 'demo' from 'demo.py'>
In [7]: dir(demo)
Out[7]:
['__builtins__',
'__doc__',
'__file__',
'__name__',
'__package__',
'c',
'd',
'print_a',
'print_b']
In [8]: demo.
demo.__builtins__ demo.__init__ demo.__str__
demo.__class__ demo.__name__ demo.__subclasshook__
demo.__delattr__ demo.__new__ demo.c
demo.__dict__ demo.__package__ demo.d
demo.__doc__ demo.__reduce__ demo.print_a
demo.__file__ demo.__reduce_ex__ demo.print_b
demo.__format__ demo.__repr__ demo.py
demo.__getattribute__ demo.__setattr__ demo.pyc
demo.__hash__ demo.__sizeof__

将对象从模块导入主命名空间

In [9]: from demo import print_a, print_b
In [10]: whos
Variable Type Data/Info
--------------------------------
demo module <module 'demo' from 'demo.py'>
print_a function <function print_a at 0xb7421534>
print_b function <function print_b at 0xb74214c4>
In [11]: print_a()
a

警告

模块缓存

模块被缓存:如果修改demo.py并在旧会话中重新导入,你将获得旧的会话。

解决办法:

In [10]: reload(demo)

在Python3中,reload不是内置的,所以你必须首先导入importlib模块,然后执行:

In [10]: importlib.reload(demo)

1.2.5.4. '__main__'和模块加载

有时,我们希望代码在模块直接运行时执行,而不是在由另一个模块导入时执行。if __name__ == '__main__'允许我们检查模块是否是被直接运行。

文件demo2.py

def print_b():
"Prints b."
print 'b'
def print_a():
"Prints a."
print 'a'
# print_b() runs on import
print_b()
if __name__ == '__main__':
# print_a() is only executed when the module is run directly.
print_a()

导入:

In [11]: import demo2
b
In [12]: import demo2

运行它:

In [13]: %run demo2
b
a

1.2.5.5. 脚本还是模块?如何组织代码

注意

经验法则

  • 为了更好的代码可重用性,多次调用的指令集应该写在函数中。
  • 由几个脚本调用的函数(或其他代码片段)应编写为模块,这样在不同的脚本只要导入该模块(不要复制粘贴你的函数到不同的脚本!)。

1.2.5.5.1. 如何找到和导入模块

当执行import mymodule语句时,将在给定的目录列表中搜索模块mymodule此列表包括与安装相关的默认路径(例如,/usr/lib/python)以及由环境变量PYTHONPATH指定的目录列表。

Python搜索的目录列表由sys.path变量给出

In [1]: import sys
In [2]: sys.path
Out[2]:
['',
'/home/varoquau/.local/bin',
'/usr/lib/python2.7',
'/home/varoquau/.local/lib/python2.7/site-packages',
'/usr/lib/python2.7/dist-packages',
'/usr/local/lib/python2.7/dist-packages',
...]

模块必须位于搜索路径中,因此你可以:

  • 在搜索路径中定义的目录(例如$HOME/.local/lib/python2.7/dist-packages)中编写自己的模块。你可以使用符号链接(在Linux上)将代码保留在其他位置。

  • 修改环境变量PYTHONPATH以包括包含用户定义模块的目录。

    在Linux/Unix上,将以下行添加到启动时由shell读取的文件(例如/etc/profile,.profile)

    export PYTHONPATH=$PYTHONPATH:/home/emma/user_defined_modules
    

    在Windows上,http://support.microsoft.com/kb/310519说明如何处理环境变量。

  • 或在Python脚本中修改sys.path变量​​本身。

    import sys
    
    new_path = '/home/emma/user_defined_modules'
    if new_path not in sys.path:
    sys.path.append(new_path)

    然而,这种方法不是很健壮,因为它使代码更不方便(依赖于用户的路径),并且因为每次要从此目录中的模块导入时,必须将目录添加到sys.path。

另见

有关模块的详细信息,请参见https://docs.python.org/tutorial/modules.html

1.2.5.6.

包含许多模块的目录称为包是具有子模块的模块(它们自身可以具有子模块等)。一个名为__init__.py(可能为空)的特殊文件告诉Python该目录是一个Python包,从中可以导入模块。

$ ls
cluster/ io/ README.txt@ stsci/
__config__.py@ LATEST.txt@ setup.py@ __svn_version__.py@
__config__.pyc lib/ setup.pyc __svn_version__.pyc
constants/ linalg/ setupscons.py@ THANKS.txt@
fftpack/ linsolve/ setupscons.pyc TOCHANGE.txt@
__init__.py@ maxentropy/ signal/ version.py@
__init__.pyc misc/ sparse/ version.pyc
INSTALL.txt@ ndimage/ spatial/ weave/
integrate/ odr/ special/
interpolate/ optimize/ stats/
$ cd ndimage
$ ls
doccer.py@ fourier.pyc interpolation.py@ morphology.pyc setup.pyc
doccer.pyc info.py@ interpolation.pyc _nd_image.so
setupscons.py@
filters.py@ info.pyc measurements.py@ _ni_support.py@
setupscons.pyc
filters.pyc __init__.py@ measurements.pyc _ni_support.pyc tests/
fourier.py@ __init__.pyc morphology.py@ setup.py@

从Ipython:

In [1]: import scipy
In [2]: scipy.__file__
Out[2]: '/usr/lib/python2.6/dist-packages/scipy/__init__.pyc'
In [3]: import scipy.version
In [4]: scipy.version.version
Out[4]: '0.7.0'
In [5]: import scipy.ndimage.morphology
In [6]: from scipy.ndimage import morphology
In [17]: morphology.binary_dilation?
Type: function
Base Class: <type 'function'>
String Form: <function binary_dilation at 0x9bedd84>
Namespace: Interactive
File: /usr/lib/python2.6/dist-packages/scipy/ndimage/morphology.py
Definition: morphology.binary_dilation(input, structure=None,
iterations=1, mask=None, output=None, border_value=0, origin=0,
brute_force=False)
Docstring:
Multi-dimensional binary dilation with the given structure.
An output array can optionally be provided. The origin parameter
controls the placement of the filter. If no structuring element is
provided an element is generated with a squared connectivity equal
to one. The dilation operation is repeated iterations times. If
iterations is less than 1, the dilation is repeated until the
result does not change anymore. If a mask is given, only those
elements with a true value at the corresponding mask element are
modified at each iteration.

1.2.5.7. 良好做法

  • 使用有意义的对象名称

  • 缩进:没有选择!

    在Python中缩进是必须的!冒号后面的每个命令块都带有相对于前一个带冒号的行的附加缩进级别。因此,def f():while:之后必须有一个缩进。在这样的逻辑块的末尾,减小缩进深度(并且如果输入新块则重新增加它)。

    严格遵守缩进是消除其他语言描述逻辑块的{;字符的代价。不当的缩进会导致错误,例如

    ------------------------------------------------------------
    
    IndentationError: unexpected indent (test.py, line 2)

    所有这种缩进业务在开始时可能有点混乱。然而,有了清晰的缩进,并且没有额外的字符,得到的代码与其他语言相比非常好阅读。

  • 缩进深度:在文本编辑器中,你可以选择缩进任何正数空格(1、2、3、4...)。但是,将缩进为4个空格是一种良好的做法。你可以配置编辑器将Tab键映射到4个空格的缩进。在Python(x,y)中,编辑器已经以这种方式配置。

  • 样式指南

    长行:你不应该写出超过(例如)80个字符的超长行。长行可以用\字符分隔

    >>> long_line = "Here is a very very long line \
    
    ... that we break in two parts."

    空格

    编写的代码具有很好的空格:在逗号后、算术运算符周围放置空格等。

    >>> a = 1 # yes
    
    >>> a=1 # too cramped

    编写“漂亮”代码的规则(更重要的是使用与其他人相同的约定!)Python代码样式指南中给出。


快速阅读

如果你想第一次快速通过Scipy讲座来学习生态系统,你可以直接跳到下一章:NumPy:创建和操作数值数据

本章的其余部分对于本简介的后面的内容不是必要的。但是一定要回来,以后完成这一章。