Dive-Into-Python-中文版-一-

100 阅读33分钟

Dive Into Python 中文版(一)

版权信息

审校 (5.4b):2007 年 6 月—9 月

译文版 (5.4):2005 年 12 月—2006 年 4 月 (update-060425)

英文原版 (5.4):2004 年 5 月 20 日

Copyright ? 2000, 2001, 2002, 2003, 2004 Mark Pilgrim

Copyright ? 2001, 2002, 2003, 2004, 2005, 2006, 2007 CPyUG (邮件列表)

本书存放在 diveintopython.org/ (英文原版) 和 www.woodpecker.org.cn/diveintopython(中文版)。如果你是从别的地方看到它的,可能看到的不是最新版本。

Permission is granted to copy, distribute, and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in 附录?G, GNU Free Documentation License.

允许在 GNU 自由文档协议 (1.1 版,或自由软件基金会出版的任何更新版本) 的许可下复制、发行且/或修改本文档;本文档没有不变部分,没有前封面文本,没有封底文本。该协议的一份中文版参考译文包含在 附录?H, GNU 自由文档协议 中。

在这本书中的例程是自由软件。你可以在遵守 Python 协议 (Python 软件基金会发布) 条款的规定下,重新发布,且/或修改它们。在 附录?I, Python license 中包含了此协议的一份拷贝。

本译本由 Zoom.Quiet 负责项目管理。感谢啄木鸟社区提供 SVN 项目空间Wiki 协作空间

本译本由 啄木鸟/CPUG 的 obp 团队完成。可以在修订历史中找到一个翻译和修订人员的清单。如果您对当前版本的 Dive Into Python 中文版有任何意见和建议,可以到本书的 Wiki 协作空间中留下你的评论。

本译文遵守 GFDL 的规定。你可以复制、发行、修改此文档,但请保留此版权信息。

第一章 安装 Python

第一章 安装 Python

  • 1.1. 哪一种 Python 适合您?
  • 1.2. Windows 上的 Python
  • 1.3. Mac OS X 上的 Python
  • 1.4. Mac OS 9 上的 Python
  • 1.5. RedHat Linux 上的 Python
  • 1.6. Debian GNU/Linux 上的 Python
  • 1.7. 从源代码安装 Python
  • 1.8. 使用 Python 的交互 Shell
  • 1.9. 小结

欢迎来到 Python 世界,让我们开始吧。在本章中,将学习适合您的 Python 安装。

1.1. 哪一种 Python 适合您?

1.1. 哪一种 Python 适合您?

学习 Python 的第一件事就是安装,不是吗?

如果您在公网的服务器上有个用户账号,那么您的 ISP 或许已经安装了 Python。 大多数 Linux 发行版在默认安装的情况下就已经提供了 Python。 虽然您可能希望在苹果机上安装一个拥有类 Mac 的图形操作界面,但在 Mac OS X 10.2 或更高的版本上已经包含了一个 Python 的命令行版本。

Windows 环境默认不提供任何版本的 Python,但是不要担心!本章将提供几种 Windows 环境下安装 Python 的方法。

正像您所看到的,Python 可以运行于很多操作系统平台。包括 Windows、Mac OS、Mac OS X、所有免费的类 UNIX 变种 (如 Linux)。也有运行于 Sun Solaris、AS/400、Amiga、OS/2、BeOS 的版本,甚至是您从来没听说过的其他操作系统平台。

有太多的平台可以运行 Python 了。在一种平台下编写的 Python 程序稍作修改,就可以运行于任何 其他支持的平台。例如,我通常在 Windows 平台上开发 Python 程序,然后适当配置后使之能在 Linux 平台上运行。

回到开始的问题,“哪一种 Python 适合您?” 回答是:哪一个已经安装在您计算机上均可。

1.2. Windows 上的 Python

1.2. Windows 上的 Python

在 Windows 上,安装 Python 有两种选择。

ActiveState 制作的 ActivePython 是专门针对 Windows 的 Python 套件,它包含了一个完整的 Python 发布、一个适用于 Python 编程的 IDE 以及一些 Python 的 Windows 扩展,提供了全部的访问 Windows APIs 的服务,以及 Windows 注册表的注册信息。

虽然 ActivePython 不是开源软件,但它可以自由下载。ActivePython 是我学习 Python 时使用过的 IDE。除非有别的原因,我建议您使用它。可能的一个原因是:ActiveState 通常要在新的 Python 版本发布几个月以后才更新它的安装程序。如果您就需要 Python 的最新版本,并且 ActivePython 仍然落后于最新版本的话,您应该直接跳到在 Windows 上安装 Python 的第二种选项。

第二种选择是使用由 Python 发布的 “官方” Python 安装程序。她是可自由下载的开源软件,并且您总是可以获得当前 Python 的最新版本。

过程 1.1. 选项 1:安装 ActivePython

下面描述 ActivePython 的安装过程:

  1. www.activestate.com/Products/ActivePython/ 下载 ActivePython 。

  2. 如果您正在使用 Windows 95、Windows 98 或 Windows ME,还需要在安装 ActivePython 之前下载并安装Windows Installer 2.0

  3. 双击安装程序 ActivePython-2.2.2-224-win32-ix86.msi

  4. 按照安装程序的提示信息一步步地执行。

  5. 如果磁盘空间不足,您可以执行定制安装,不选文档,但是笔者不建议您这样做,除非您实在是挤不出 14M 空间来。

  6. 在安装完后之后,关闭安装程序,打开 开始->程序->ActiveState ActivePython 2.2->PythonWin IDE。您将看到类似如下的信息:

PythonWin 2.2.2 (#37, Nov 26 2002, 10:24:37) [MSC 32 bit (Intel)] on win32.
Portions Copyright 1994-2001 Mark Hammond (mhammond@skippinet.com.au) -
see 'Help/About PythonWin' for further copyright information.
>>> 

过程 1.2. 选项 2:安装来自 Python.org 的 Python

  1. www.python.org/ftp/python/ 选择最新的 Python Windows 安装程序,下载 .exe 安装文件。

  2. 双击安装程序 Python-2.xxx.yyy.exe。文件名依赖于您所下载的 Python 安装程序文件。

  3. 按照安装程序的提示信息一步步地执行。

  4. 如果磁盘空间不足,可以取消 HTMLHelp 文件、实用脚本 (Tools/)、和/或测试套件 (Lib/test/)。

  5. 如果您没有机器的管理员权限,您可以选择 Advanced Options,然后选择 Non-Admin Install。这只会对登记注册表和开始菜单中创建的快捷方式有影响。

  6. 在安装完成之后,关闭安装程序,打开 开始->程序->Python 2.3->IDLE (Python GUI)。您将看到类似如下的信息:

Python 2.3.2 (#49, Oct  2 2003, 20:02:00) [MSC v.1200 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
    ****************************************************************
    Personal firewall software may warn about the connection IDLE
    makes to its subprocess using this computer's internal loopback
    interface.  This connection is not visible on any external
    interface and no data is sent to or received from the Internet.
    ****************************************************************
IDLE 1.0
>>> 

1.3. Mac OS X 上的 Python

1.3. Mac OS X 上的 Python

在 Mac OS X 上,对于安装 Python 有两种选择:安装或不安装。您可能想要安装它。

Mac OS X 10.2 及其后续版本已经预装了一个 Python 的命令行版本。如果您习惯使用命令行,那么您可以使用它学完本书的三分之一。然而,预安装的版本不带 XML 解析器,所以当您学到 XML 的章节时,您会需要安装完整版。

您还可以安装优于预装版本的最新的包含图形界面 Shell 的完整版本。

过程 1.3. 在 Mac OS X 上运行预装版本的 Python

使用预装的 Python 版本的步骤:

  1. 打开 /Applications 文件夹。

  2. 打开 Utilities 文件夹。

  3. 双击 Terminal 打开一个终端进入命令行窗口。

  4. 在提示符下键入 python

试验:

Welcome to Darwin!
[localhost:~] you% python
Python 2.2 (#1, 07/14/02, 23:25:09)
[GCC Apple cpp-precomp 6.14] on darwin
Type "help", "copyright", "credits", or "license" for more information.
>>> [press Ctrl+D to get back to the command prompt]
[localhost:~] you% 

过程 1.4. 在 Mac OS X 上安装最新版的 Python

下面介绍下载并安装 Python 最新版本的过程:

  1. homepages.cwi.nl/~jack/macpython/download.html 下载 MacPython-OSX 磁盘镜像 。

  2. 下载完毕,双击 MacPython-OSX-2.3-1.dmg 将磁盘镜像挂载到桌面。

  3. 双击安装程序 MacPython-OSX.pkg.

  4. 安装程序将提示要求您的管理员用户名和口令。

  5. 按照安装程序的提示一步步执行。

  6. 安装完毕后,关闭安装程序,打开 /Applications 文件夹。

  7. 打开 MacPython-2.3 文件夹。

  8. 双击 PythonIDE 来运行 Python 。

MacPython IDE 将显示一个弹出屏幕界面将您带进交互 shell。如果交互 shell 没有出现,选择 Window->Python Interactive (Cmd-0)。您将看到类似如下的信息:

Python 2.3 (#2, Jul 30 2003, 11:45:28)
[GCC 3.1 20020420 (prerelease)]
Type "copyright", "credits" or "license" for more information.
MacPython IDE 1.0.1
>>> 

请注意,安装完最新版本后,预装版本仍然存在。如果您从命令行运行脚本,那您需要知道正在使用的是哪一个版本的 Python 。

例 1.1. 两个 Python 版本

[localhost:~] you% python
Python 2.2 (#1, 07/14/02, 23:25:09)
[GCC Apple cpp-precomp 6.14] on darwin
Type "help", "copyright", "credits", or "license" for more information.
>>> [press Ctrl+D to get back to the command prompt]
[localhost:~] you% /usr/local/bin/python
Python 2.3 (#2, Jul 30 2003, 11:45:28)
[GCC 3.1 20020420 (prerelease)] on darwin
Type "help", "copyright", "credits", or "license" for more information.
>>> [press Ctrl+D to get back to the command prompt]
[localhost:~] you% 

1.4. Mac OS 9 上的 Python

1.4. Mac OS 9 上的 Python

Mac OS 9 上没有预装任何版本的 Python,安装相对简单,只有一种选择。

下面介绍在 Mac OS 9 上安装 Python 的过程:

  1. homepages.cwi.nl/~jack/macpython/download.html 下载 MacPython23full.bin

  2. 如果浏览器不能自动解压文件,那么双击 MacPython23full.bin 用 Stuffit Expander 解压。

  3. 双击安装程序 MacPython23full

  4. 按照安装程序的提示一步步执行。

  5. 安装完毕后,关闭安装程序,打开 /Applications 文件夹。

  6. 打开 MacPython-OS9 2.3 文件夹。

  7. 双击 PythonIDE 来运行 Python 。

MacPython IDE 将显示一个弹出屏幕界面将您带进交互 shell。如果交互 shell 没有出现,选择 Window->Python Interactive (Cmd-0)。您将看到类似如下的信息:

Python 2.3 (#2, Jul 30 2003, 11:45:28)
[GCC 3.1 20020420 (prerelease)]
Type "copyright", "credits" or "license" for more information.
MacPython IDE 1.0.1
>>> 

1.5. RedHat Linux 上的 Python

1.5. RedHat Linux 上的 Python

在类 UNIX 的操作系统 (如 Linux) 上安装二进制包很容易。预编译好的二进制包对大多数 Linux 发行版是可用的。或者您可以通过源码进行编译。

www.python.org/ftp/python/ 选择列出的最新的版本号, 然后选择 其中的rpms/ 目录下载最新的 Python RPM 包。 使用 rpm 命令进行安装,操作如下所示:

例 1.2. 在 RedHat Linux 9 上安装

localhost:~$ su -
Password: [enter your root password]
[root@localhost root]# wget http://python.org/ftp/python/2.3/rpms/redhat-9/python2.3-2.3-5pydotorg.i386.rpm
Resolving python.org... done.
Connecting to python.org[194.109.137.226]:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7,495,111 [application/octet-stream]
...
[root@localhost root]# rpm -Uvh python2.3-2.3-5pydotorg.i386.rpm
Preparing...                ########################################### [100%]
   1:python2.3              ########################################### [100%]
[root@localhost root]# python          
Python 2.2.2 (#1, Feb 24 2003, 19:13:11)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-4)] on linux2
Type "help", "copyright", "credits", or "license" for more information.
>>> [press Ctrl+D to exit]
[root@localhost root]# python2.3       
Python 2.3 (#1, Sep 12 2003, 10:53:56)
[GCC 3.2.2 20030222 (Red Hat Linux 3.2.2-5)] on linux2
Type "help", "copyright", "credits", or "license" for more information.
>>> [press Ctrl+D to exit]
[root@localhost root]# which python2.3 
/usr/bin/python2.3 
[1]仅仅键入 python 运行的是老版本的 Python ――它是缺省安装的版本。它不是我们想要的。
[2]截止到笔者写作时,新的版本是 python2.3。您可能会需要修改示例脚本的第一行的路径指向新版本。
[3]这是我们刚安装的 Python 新版本的全路径。在 #! 行中 (每个脚本的第一行) 使用它来确保脚本运行在最新版的 Python 下,并且确保敲入的是 python2.3 进入交互 shell。

1.6. Debian GNU/Linux 上的 Python

1.6. Debian GNU/Linux 上的 Python

如果您运行在 Debian GNU/Linux 上,安装 Python 需要使用 apt 命令。

例 1.3. 在 Debian GNU/Linux 上安装

localhost:~$ su -
Password: [enter your root password]
localhost:~# apt-get install python
Reading Package Lists... Done
Building Dependency Tree... Done
The following extra packages will be installed:
  python2.3
Suggested packages:
  python-tk python2.3-doc
The following NEW packages will be installed:
  python python2.3
0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded.
Need to get 0B/2880kB of archives.
After unpacking 9351kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Selecting previously deselected package python2.3.
(Reading database ... 22848 files and directories currently installed.)
Unpacking python2.3 (from .../python2.3_2.3.1-1_i386.deb) ...
Selecting previously deselected package python.
Unpacking python (from .../python_2.3.1-1_all.deb) ...
Setting up python (2.3.1-1) ...
Setting up python2.3 (2.3.1-1) ...
Compiling python modules in /usr/lib/python2.3 ...
Compiling optimized python modules in /usr/lib/python2.3 ...
localhost:~# exit
logout
localhost:~$ python
Python 2.3.1 (#2, Sep 24 2003, 11:39:14)
[GCC 3.3.2 20030908 (Debian prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> [press Ctrl+D to exit] 

1.7. 从源代码安装 Python

1.7. 从源代码安装 Python

如果您宁愿从源码创建,可以从 www.python.org/ftp/python/下载 Python 的源代码。选择最新的版本,下载.tgz 文件,执行通常的 configure, make, make install 步骤。

例 1.4. 从源代码安装

localhost:~$ su -
Password: [enter your root password]
localhost:~# wget http://www.python.org/ftp/python/2.3/Python-2.3.tgz
Resolving www.python.org... done.
Connecting to www.python.org[194.109.137.226]:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 8,436,880 [application/x-tar]
...
localhost:~# tar xfz Python-2.3.tgz
localhost:~# cd Python-2.3
localhost:~/Python-2.3# ./configure
checking MACHDEP... linux2
checking EXTRAPLATDIR...
checking for --without-gcc... no
...
localhost:~/Python-2.3# make
gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes
-I. -I./Include  -DPy_BUILD_CORE -o Modules/python.o Modules/python.c
gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes
-I. -I./Include  -DPy_BUILD_CORE -o Parser/acceler.o Parser/acceler.c
gcc -pthread -c -fno-strict-aliasing -DNDEBUG -g -O3 -Wall -Wstrict-prototypes
-I. -I./Include  -DPy_BUILD_CORE -o Parser/grammar1.o Parser/grammar1.c
...
localhost:~/Python-2.3# make install
/usr/bin/install -c python /usr/local/bin/python2.3
...
localhost:~/Python-2.3# exit
logout
localhost:~$ which python
/usr/local/bin/python
localhost:~$ python
Python 2.3.1 (#2, Sep 24 2003, 11:39:14)
[GCC 3.3.2 20030908 (Debian prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> [press Ctrl+D to get back to the command prompt]
localhost:~$ 

1.8. 使用 Python 的交互 Shell

1.8. 使用 Python 的交互 Shell

既然我们已经安装了 Python,那么我们运行的这个交互 shell 是什么东西呢?

Python 扮演着两种角色。首先它是一个脚本解释器,可以从命令行运行脚本,也可以在脚本上双击,像运行其他应用程序一样。它还是一个交互 shell,可以执行任意的语句和表达式。这一点对调试、快速组建和测试相当有用。我甚至知道一些人把 Python 的交互 shell 当作计算器来使用!

在您的计算机平台上启动 Python 的交互 shell,接下来让我们尝试着做些操作:

例 1.5. 初次使用交互 Shell

>>> 1 + 1               
2
>>> print 'hello world' 
hello world
>>> x = 1               
>>> y = 2
>>> x + y
3 
[1]Python 的交互 shell 可以计算任意的 Python 表达式,包括任何基本的数学表达式。
[2]交互 shell 可以执行任意的 Python 语句,包括 print 语句。
[3]也可以给变量赋值,并且变量值在 shell 打开时一直有效 (一旦关毕交互 Sheel,变量值将丢失)。

1.9. 小结

1.9. 小结

您现在应该已经安装了一个可以工作的 Python 版本了。

根据您的运行平台,您可能安装有不止一个 Python 版本。那样的话,您需要知道 Python 的路径。若在命令行简单地键入 python 没有运行您想使用的 Python 版本,则需要输入想要的版本的全路径。

最后祝贺您,欢迎来到 Python 世界。

第二章 第一个 Python 程序

第二章 第一个 Python 程序

  • 2.1. 概览
  • 2.2. 函数声明
    • 2.2.1. Python 和其他编程语言数据类型的比较
  • 2.3. 文档化函数
  • 2.4. 万物皆对象
    • 2.4.1. 模块导入的搜索路径
    • 2.4.2. 何谓对象?
  • 2.5. 代码缩进
  • 2.6. 测试模块

大家都很清楚,其他书籍是如何一步步从编程基础讲述到构建完整的可运行程序的,但还是让我们跳过这个部分吧!

2.1. 概览

2.1. 概览

这是一个完整的、可执行的 Python 程序。

它可能对您来说根本无法理解。别着急,我们将逐行地进行剖析。不过首先把代码通读一遍,看一看是否有些可以理解的内容。

例 2.1. odbchelper.py

如果您还没有下载本书附带的样例程序, 可以 下载本程序和其他样例程序

 def buildConnectionString(params):
    """Build a connection string from a dictionary of parameters.
    Returns string."""
    return ";".join(["%s=%s" % (k, v) for k, v in params.items()])
if __name__ == "__main__":
    myParams = {"server":"mpilgrim", \
                "database":"master", \
                "uid":"sa", \
                "pwd":"secret" \
                }
    print buildConnectionString(myParams) 

现在运行一下这个程序,看一看结果是什么。

提示 在 Windows 的 ActivePython IDE 中,可以选择 File->Run... (Ctrl-R) 来运行 Python 程序。输出结果将显示在交互窗口中。

提示 在 Mac OS 的 Python IDE 中,可以选择 Python->Run window... (Cmd-R) 来运行 Python 程序,但首先要设置一个重要的选项。在 IDE 中打开 .py 模块,点击窗口右上角的黑色三角,弹出这个模块的选项菜单,然后将 Run as main 选中。 这个设置是同模块一同保存的,所以对于每个模块您都需要这样做。

提示 在 UNIX 兼容的操作系统中 (包括 Mac OS X),可以通过命令行:pythonodbchelper.py`` 运行模块。

odbchelper.py 的输出结果:

server=mpilgrim;uid=sa;database=master;pwd=secret 

2.2. 函数声明

2.2. 函数声明

  • 2.2.1. Python 和其他编程语言数据类型的比较

与其它大多数语言一样 Python 有函数,但是它没有像 C++ 一样的独立的头文件;或者像 Pascal 一样的分离的 interface/implementation 段。在需要函数时,像下面这样声明即可:

 def buildConnectionString(params): 

首先,函数声明以关键字 def 开始,接着为函数名,再往后为参数,参数放在小括号里。多个参数之间 (这里没有演示)用逗号分隔。

其次,函数没有定义返回的数据类型。Python 不需要指定返回值的数据类型;甚至不需要指定是否有返回值。实际上,每个 Python 函数都返回一个值;如果函数执行过 return 语句,它将返回指定的值,否则将返回 None (Python 的空值)。

注意 在 Visual Basic 中,函数 (有返回值) 以 function 开始,而子程序 (无返回值) 以 sub 开始。在 Python 中没有子程序。只有函数,所有的函数都有返回值 (尽管可能为 None),并且所有的函数都以 def 开始。

最后需要指出的是,在 Python 中参数,params 不需要指定数据类型。Python 会判定一个变量是什么类型,并在内部将其记录下来。

注意 在 Java、C++ 和其他静态类型语言中,必须要指定函数返回值和每个函数参数的数据类型。在 Python 中,永远也不需要明确指定任何东西的数据类型。Python 会根据赋给它的值在内部将其数据类型记录下来。

2.2.1. Python 和其他编程语言数据类型的比较

一位博学的读者发给我 Python 如何与其它编程语言的比较的解释:

静态类型语言

一种在编译期间就确定数据类型的语言。大多数静态类型语言是通过要求在使用任一变量之前声明其数据类型来保证这一点的。Java 和 C 是静态类型语言。

动态类型语言

一种在运行期间才去确定数据类型的语言,与静态类型相反。VBScript 和 Python 是动态类型的,因为它们确定一个变量的类型是在您第一次给它赋值的时候。

强类型语言

一种总是强制类型定义的语言。Java 和 Python 是强制类型定义的。您有一个整数,如果不明确地进行转换 ,不能将把它当成一个字符串。

弱类型语言

一种类型可以被忽略的语言,与强类型相反。VBScript 是弱类型的。在 VBScript 中,您可以将字符串 '12' 和整数 3 进行连接得到字符串'123',然后可以把它看成整数 123 ,所有这些都不需要任何的显示转换。

所以说 Python 既是动态类型语言 (因为它不使用显示数据类型声明),又是强类型语言 (因为只要一个变量获得了一个数据类型,它实际上就一直是这个类型了)。

2.3. 文档化函数

2.3. 文档化函数

可以通过给出一个 doc string (文档字符串) 来文档化一个 Python 函数。

例 2.2. 定义 buildConnectionString 函数的 doc string

 def buildConnectionString(params):
    """Build a connection string from a dictionary of parameters.
    Returns string.""" 

三重引号表示一个多行字符串。在开始与结束引号间的所有东西都被视为单个字符串的一部分,包括硬回车和其它的引号字符。您可以在任何地方使用它们,但是您可能会发现,它们经常被用于定义 doc string

注意 三重引号也是一种定义既包含单引号又包含双引号的字符串的简单方法,就像 Perl 中的 qq/.../

在三重引号中的任何东西都是这个函数的 doc string,它们用来说明函数可以做什么。如果存在 doc string,它必须是一个函数要定义的第一个内容 (也就是说,在冒号后面的第一个内容)。在技术上不要求给出函数的 doc string,但是您应该这样做。我相信在您上过的每一种编程课上都听到过这一点,但是 Python 带给您一些额外的动机:doc string 在运行时可作为函数的属性。

注意 许多 Python IDE 使用 doc string 来提供上下文敏感的文档信息,所以当键入一个函数名时,它的 doc string 显示为一个工具提示。这一点可以说非常有用,但是它的好坏取决于您书写的 doc string 的好坏。

进一步阅读

2.4. 万物皆对象

2.4. 万物皆对象

  • 2.4.1. 模块导入的搜索路径
  • 2.4.2. 何谓对象?

也许您没在意,我刚才的意思是 Python 函数有属性,并且这些属性在运行时是可用的。

在 Python 中,函数同其它东西一样也是对象。

打开您习惯使用的 Python IDE 执行如下的操作:

例 2.3. 访问 buildConnectionString 函数的 doc string

>>> import odbchelper                              
>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"}
>>> print odbchelper.buildConnectionString(params) 
server=mpilgrim;uid=sa;database=master;pwd=secret
>>> print odbchelper.buildConnectionString.__doc__ 
Build a connection string from a dictionary
Returns string. 
[1]第一行将 odbchelper 程序作为模块导入。模块是指一个可以交互使用,或者从另一 Python 程序访问的代码段。(您在 第四章 将会看到多模块 Python 程序的许多例子。) 只要导入了一个模块,就可以引用它的任何公共的函数、类或属性。模块可以通过这种方法来使用其它模块的功能,您也可以在 IDE 中这样做。这是一个很重要的概念,在后面我们将谈得更多。
[2]当使用在被导入模块中定义的函数时,必须包含模块的名字。所以不能只使用 buildConnectionString,而应该使用 odbchelper.buildConnectionString。如果您用过 Java 的类,对此应该不感到陌生。
[3]访问函数的 __doc__ 属性不像您想象的那样是通过函数调用。

注意 在 Python 中的 import 就像 Perl 中的 requireimport 一个 Python 模块后,您就可以使用 _module_._function_ 来访问它的函数;require 一个 Perl 模块后,您就可以使用 _module_::_function_ 来访问它的函数。

2.4.1. 模块导入的搜索路径

在我们继续之前,我想简要地提一下库的搜索路径。当导入一个模块时,Python 在几个地方进行搜索。明确地,它会对定义在 sys.path 中的目录逐个进行搜索。它只是一个 list (列表),您可以容易地查看它或通过标准的 list 方法来修改它。(在本章的后面我们将学习更多关于 list 的知识。)

例 2.4. 模块导入的搜索路径

>>> import sys                 
>>> sys.path                   
['', '/usr/local/lib/python2.2', '/usr/local/lib/python2.2/plat-linux2',
'/usr/local/lib/python2.2/lib-dynload', '/usr/local/lib/python2.2/site-packages',
'/usr/local/lib/python2.2/site-packages/PIL', '/usr/local/lib/python2.2/site-packages/piddle']
>>> sys                        
<module 'sys' (built-in)>
>>> sys.path.append('/my/new/path') 
[1]导入 sys 模块,使得它的所有函数和属性都有效。
[2]sys.path 是一个指定当前搜索路径的目录列表。(您的输出结果可能有所不同,这取决于您的操作系统、正在运行的 Python 版本和初始安装的位置。)Python 将搜索这些目录 (按顺序) 来查找一个与您正试着导入的模块名相匹配的 .py 文件。
[3]实际上,我没说实话。真实情况要比这更复杂,因为不是所有的模块都保存为 .py 文件。有一些模块 (像 sys),是“内置模块”,它们实际上是置于 Python 内部的。内置模块的行为如同一般的模块,但是它们的 Python 源代码是不可用的,因为它们不是用 Python 写的!(sys 模块是用 C 写的。)
[4]在运行时,通过向 sys.path 追加目录名,就可以在 Python 的搜索路径中增加新的目录,然后当您导入模块时,Python 也会在那个目录中进行搜索。这个作用在 Python 运行时一直生效。(在 第三章 我们将讨论更多的关于 append 和其它的 list 方法。)

2.4.2. 何谓对象?

在 Python 中一切都是对象,并且几乎一切都有属性和方法。所有的函数都有一个内置的 __doc__ 属性,它会返回在函数源代码中定义的 doc stringsys 模块是一个对象,它有一个叫作 path 的属性;等等。

我们仍然在回避问题的实质,究竟何谓对象?不同的编程语言以不同的方式定义 “对象” 。 某些语言中,它意味着所有 对象必须 有属性和方法;另一些语言中,它意味着所有的对象都可以子类化。在 Python 中,定义是松散的;某些对象既没有属性也没有方法 (关于这一点的说明在 第三章),而且不是所有的对象都可以子类化 (关于这一点的说明在第五章)。但是万物皆对象从感性上可以解释为:一切都可以赋值给变量或作为参数传递给函数 (关于这一点的说明在第四章)。

这一点太重要了,所以我会在刚开始就不止一次地反复强调它,以免您没注意到:在 Python 中万物皆对象。字符串是对象。列表是对象。函数是对象。甚至模块也是对象,这一点我们很快会看到。

进一步阅读

2.5. 代码缩进

2.5. 代码缩进

Python 函数没有明显的 beginend,没有标明函数的开始和结束的花括号。唯一的分隔符是一个冒号 (:),接着代码本身是缩进的。

例 2.5. 缩进 buildConnectionString 函数

 def buildConnectionString(params):
    """Build a connection string from a dictionary of parameters.
    Returns string."""
    return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) 

代码块是通过它们的缩进来定义的。我所说的“代码块”是指:函数、if 语句、for 循环、while 循环,等等。开始缩进表示块的开始,取消缩进表示块的结束。不存在明显的括号,大括号或关键字。这就意味着空白是重要的,并且要一致。在这个例子中,函数代码 (包括 doc string) 缩进了 4 个空格。不一定非要是 4 个,只要一致就可以了。没有缩进的第一行则被视为在函数体之外。

例 2.6 “if 语句” 展示了一个 if 语句缩进的例子。

例 2.6. if 语句

 def fib(n):                   
    print 'n =', n            
    if n > 1:                 
        return n * fib(n - 1)
    else:                     
        print 'end of the line'
        return 1 
[1]这是一个名为 fib 的函数,有一个参数 n。在函数内的所有代码都是缩进的。
[2]在 Python 中向屏幕输出内容非常容易,只要使用 print 即可。print 语句可以接受任何数据类型,包括字符串、整数和其它类型,如字典和列表 (我们将在下一章学习)。甚至可以混在一起输出,只需用逗号隔开。所有值都输出到同一行,用空格隔开 (逗号并不打印出来)。所以当用 5 来调用 fib 时,将输出“n = 5”。
[3]if 语句是一种的代码块。如果 if 表达式计算为 true,紧跟着的缩进块会被执行,否则进入 else 块执行。
[4]当然 ifelse 块可以包含许多行,只要它们都同样缩进。这个 else 块中有两行代码。对于多行代码块没有其它特殊的语法,只要缩进就行了。

在经过一些最初的抗议和几个与 Fortran 的嘲讽的类比之后,您会心平气和地对待代码缩进,并且开始看到它的好处。一个主要的好处就是所有的 Python 程序看上去都差不多,因为缩进是一种语言的要求而不是一种风格。这样就使得阅读和理解他人的 Python 代码容易得多。

注意 Python 使用硬回车来分割语句,冒号和缩进来分割代码块。C++ 和 Java 使用分号来分割语句,花括号来分割代码块。

进一步阅读

2.6. 测试模块

2.6. 测试模块

所有的 Python 模块都是对象,并且有几个有用的属性。您可以使用这些属性方便地测试您所编写的模块。下面是一个使用 if __name__ 的技巧。

 if __name__ == "__main__": 

在继续学习新东西之前,有几个重要的观察结果。首先,if 表达式无需使用圆括号括起来。其次,if 语句以冒号结束,紧跟其后的是缩进代码。

注意 与 C 一样,Python 使用 == 做比较,使用 = 做赋值。与 C 不一样,Python 不支持行内赋值,所以不会出现想要进行比较却意外地出现赋值的情况。

那么为什么说这个特殊的 if 语句是一个技巧呢?模块是对象,并且所有的模块都有一个内置属性 __name__。一个模块的 __name__ 的值取决于您如何应用模块。如果 import 模块,那么 __name__ 的值通常为模块的文件名,不带路径或者文件扩展名。但是您也可以像一个标准的程序一样直接运行模块,在这种情况下 __name__ 的值将是一个特别的缺省值,__main__

>>> import odbchelper
>>> odbchelper.`__name__`
'odbchelper' 

只要了解到这一点,您就可以在模块内部为您的模块设计一个测试套件,在其中加入这个 if 语句。当您直接运行模块,__name__ 的值是 __main__,所以测试套件执行。当您导入模块,__name__ 的值就是别的东西了,所以测试套件被忽略。这样使得在将新的模块集成到一个大程序之前开发和调试容易多了。

提示 在 MacPython 上,需要一个额外的步聚来使得 if __name__ 技巧有效。点击窗口右上角的黑色三角,弹出模块的属性菜单,确认 Run as main 被选中。

进一步阅读

  • Python Reference Manual 讨论了导入模块的底层细节。

第三章 内置数据类型

第三章 内置数据类型

  • 3.1. Dictionary 介绍
    • 3.1.1. Dictionary 的定义
    • 3.1.2. Dictionary 的修改
    • 3.1.3. 从 dictionary 中删除元素
  • 3.2. List 介绍
    • 3.2.1. List 的定义
    • 3.2.2. 向 list 中增加元素
    • 3.2.3. 在 list 中搜索
    • 3.2.4. 从 list 中删除元素
    • 3.2.5. 使用 list 的运算符
  • 3.3. Tuple 介绍
  • 3.4. 变量声明
    • 3.4.1. 变量引用
    • 3.4.2. 一次赋多值
  • 3.5. 格式化字符串
  • 3.6. 映射 list
  • 3.7. 连接 list 与分割字符串
    • 3.7.1. 字符串方法的历史注解
  • 3.8. 小结

让我们用点儿时间来回顾一下您的第一个 Python 程序。但首先,先说些其他的内容,因为您需要了解一下 dictionary (字典)、tuple (元组) 和 list (列表)(哦,我的老天!)。如果您是一个 Perl hacker,当然可以撇开 dictionary 和 list,但是仍然需要注意 tuple。

3.1. Dictionary 介绍

3.1. Dictionary 介绍

  • 3.1.1. Dictionary 的定义
  • 3.1.2. Dictionary 的修改
  • 3.1.3. 从 dictionary 中删除元素

Dictionary 是 Python 的内置数据类型之一,它定义了键和值之间一对一的关系。

注意 Python 中的 dictionary 就像 Perl 中的 hash (哈希数组)。在 Perl 中,存储哈希值的变量总是以 % 字符开始;在 Python 中,变量可以任意取名,并且 Python 在内部会记录下其数据类型。

注意 Python 中的 dictionary 像 Java 中的 Hashtable 类的实例。

注意 Python 中的 dictionary 像 Visual Basic 中的 Scripting.Dictionary 对象的实例。

3.1.1. Dictionary 的定义

例 3.1. 定义 Dictionary

>>> d = {"server":"mpilgrim", "database":"master"} 
>>> d
{'server': 'mpilgrim', 'database': 'master'}
>>> d["server"]                                    
'mpilgrim'
>>> d["database"]                                  
'master'
>>> d["mpilgrim"]                                  
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
KeyError: mpilgrim 
[1]首先我们创建了新 dictionary,它有两个元素,将其赋给变量 d 。每一个元素都是一个 key-value 对;整个元素集合用大括号括起来。
[2]'server' 是一个 key,它所关联的值是通过 d["server"] 来引用的,为 'mpilgrim'
[3]'database' 是一个 key,它所关联的值是通过 d["database"] 来引用的,为 'master'
[4]您可以通过 key 来引用其值,但是不能通过值获取 key。所以 d["server"] 的值为 'mpilgrim',而使用 d["mpilgrim"] 会引发一个异常,因为 'mpilgrim' 不是一个 key。

3.1.2. Dictionary 的修改

例 3.2. 修改 Dictionary

>>> d
{'server': 'mpilgrim', 'database': 'master'}
>>> d["database"] = "pubs" 
>>> d
{'server': 'mpilgrim', 'database': 'pubs'}
>>> d["uid"] = "sa"        
>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'pubs'} 
[1]在一个 dictionary 中不能有重复的 key。给一个存在的 key 赋值会覆盖原有的值。
[2]在任何时候都可以加入新的 key-value 对。这种语法同修改存在的值是一样的。(是的,它可能某天会给您带来麻烦。假设你一次次地修改一个 dictionary,但其间您使用的 key 并未按照您的想法进行改变。您可能以为加入了新值,但实际上只是一次又一次地修改了同一个值。)

请注意新的元素 (key 为 'uid',value 为 'sa') 出现在中间。实际上,在第一个例子中的元素看上去是的有序不过是一种巧合。现在它们看上去的无序同样是一种巧合。

注意 Dictionary 没有元素顺序的概念。说元素 “顺序乱了” 是不正确的,它们只是序偶的简单排列。这是一个重要的特性,它会在您想要以一种特定的,可重现的顺序 (像以 key 的字母表顺序) 存取 dictionary 元素的时候骚扰您。有一些实现这些要求的方法,它们只是没有加到 dictionary 中去。

当使用 dictionary 时,您需要知道:dictionary 的 key 是大小写敏感的。

例 3.3. Dictionary 的 key 是大小写敏感的

>>> d = {}
>>> d["key"] = "value"
>>> d["key"] = "other value" 
>>> d
{'key': 'other value'}
>>> d["Key"] = "third value" 
>>> d
{'Key': 'third value', 'key': 'other value'} 
[1]为一个已经存在的 dictionary key 赋值,将简单覆盖原有的值。
[2]这不会为一个已经存在的 dictionary key 赋值,因为在 Python 中是区分大小写的,也就是说 'key''Key' 是不同的。所以这种情况将在 dictionary 中创建一个新的 key-value 对。虽然看上去很相近,但是在 Python 眼里是完全不同的。

例 3.4. 在 dictionary 中混用数据类型

>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'pubs'}
>>> d["retrycount"] = 3 
>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 'retrycount': 3}
>>> d[42] = "douglas"   
>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'master',
42: 'douglas', 'retrycount': 3} 
[1]Dictionary 不只是用于存储字符串。Dictionary 的值可以是任意数据类型,包括字符串、整数、对象,甚至其它的 dictionary。在单个 dictionary 里,dictionary 的值并不需要全都是同一数据类型,可以根据需要混用和匹配。
[2]Dictionary 的 key 要严格多了,但是它们可以是字符串、整数或几种其它的类型 (后面还会谈到这一点)。也可以在一个 dictionary 中混用和匹配 key 的数据类型。

3.1.3. 从 dictionary 中删除元素

例 3.5. 从 dictionary 中删除元素

>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'master',
42: 'douglas', 'retrycount': 3}
>>> del d[42] 
>>> d
{'server': 'mpilgrim', 'uid': 'sa', 'database': 'master', 'retrycount': 3}
>>> d.clear() 
>>> d
{} 
[1]del 允许您使用 key 从一个 dictionary 中删除独立的元素。
[2]clear 从一个 dictionary 中清除所有元素。注意空的大括号集合表示一个没有元素的 dictionary。

进一步阅读

3.2. List 介绍

3.2. List 介绍

  • 3.2.1. List 的定义
  • 3.2.2. 向 list 中增加元素
  • 3.2.3. 在 list 中搜索
  • 3.2.4. 从 list 中删除元素
  • 3.2.5. 使用 list 的运算符

List 是 Python 中使用最频繁的数据类型。如果您对 list 仅有的经验就是在 Visual Basic 中的数组或 Powerbuilder 中的数据存储,那么就打起精神学习 Python 的 list 吧。

注意 Python 的 list 如同 Perl 中的数组。在 Perl 中,用来保存数组的变量总是以 @ 字符开始;在 Python 中,变量可以任意取名,并且 Python 在内部会记录下其数据类型。

注意 Python 中的 list 更像 Java 中的数组 (您可以简单地这样理解,但 Python 中的 list 远比 Java 中的数组强大)。一个更好的类比是 ArrayList 类,它可以保存任意对象,并且可以在增加新元素时动态扩展。

3.2.1. List 的定义

例 3.6. 定义 List

>>> li = ["a", "b", "mpilgrim", "z", "example"] 
>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[0]                                       
'a'
>>> li[4]                                       
'example' 
[1]首先我们定义了一个有 5 个元素的 list。注意它们保持着初始的顺序。这不是偶然。List 是一个用方括号包括起来的有序元素的集合。
[2]List 可以作为以 0 下标开始的数组。任何一个非空 list 的第一个元素总是 li[0]
[3]这个包含 5 个元素 list 的最后一个元素是 li[4],因为列表总是从 0 开始。

例 3.7. 负的 list 索引

>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[-1] 
'example'
>>> li[-3] 
'mpilgrim' 
[1]负数索引从 list 的尾部开始向前计数来存取元素。任何一个非空的 list 最后一个元素总是 li[-1]
[2]如果负数索引使您感到糊涂,可以这样理解:li[-n] == li[len(li) - n]。所以在这个 list 里,li[-3] == li[5 - 3] == li[2]

例 3.8. list 的分片 (slice)

>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[1:3]  
['b', 'mpilgrim']
>>> li[1:-1] 
['b', 'mpilgrim', 'z']
>>> li[0:3]  
['a', 'b', 'mpilgrim'] 
[1]您可以通过指定 2 个索引得到 list 的子集,叫做一个 “slice” 。返回值是一个新的 list,它包含了 list 中按顺序从第一个 slice 索引 (这里为 li[1]) 开始,直到但是不包括第二个 slice 索引 (这里为 li[3]) 的所有元素。
[2]如果一个或两个 slice 索引是负数,slice 也可以工作。如果对您有帮助,您可以这样理解:从左向右阅读 list,第一个 slice 索引指定了您想要的第一个元素,第二个 slice 索引指定了第一个您不想要的元素。返回的值为在其间的每个元素。
[3]List 从 0 开始,所以 li[0:3] 返回 list 的前 3 个元素,从 li[0] 开始,直到但不包括 li[3]

例 3.9. Slice 简写

>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li[:3] 
['a', 'b', 'mpilgrim']
>>> li[3:]  
['z', 'example']
>>> li[:]  
['a', 'b', 'mpilgrim', 'z', 'example'] 
[1]如果左侧分片索引为 0,您可以将其省略,默认为 0。所以 li[:3] 同 例 3.8 “list 的分片 (slice)”") 的 li[0:3] 是一样的。
[2]同样的,如果右侧分片索引是 list 的长度,可以将其省略。所以 li[3:]li[3:5] 是一样的,因为这个 list 有 5 个元素。
[3]请注意这里的对称性。在这个包含 5 个元素的 list 中,li[:3] 返回前 3 个元素,而 li[3:] 返回后 2 个元素。实际上,li[:n] 总是返回前 n 个元素,而 li[n:] 将返回剩下的元素,不管 list 有多长。
[4]如果将两个分片索引全部省略,这将包括 list 的所有元素。但是与原始的名为 li 的 list 不同,它是一个新 list,恰好拥有与 li 一样的全部元素。li[:] 是生成一个 list 完全拷贝的一个简写。

3.2.2. 向 list 中增加元素

例 3.10. 向 list 中增加元素

>>> li
['a', 'b', 'mpilgrim', 'z', 'example']
>>> li.append("new")               
>>> li
['a', 'b', 'mpilgrim', 'z', 'example', 'new']
>>> li.insert(2, "new")            
>>> li
['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new']
>>> li.extend(["two", "elements"]) 
>>> li
['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements'] 
[1]append 向 list 的末尾追加单个元素。
[2]insert 将单个元素插入到 list 中。数值参数是插入点的索引。请注意,list 中的元素不必唯一,现在有两个独立的元素具有 'new' 这个值,li[2]li[6]
[3]extend 用来连接 list。请注意不要使用多个参数来调用 extend,要使用一个 list 参数进行调用。在本例中,这个 list 有两个元素。

例 3.11. extend (扩展) 与 append (追加) 的差别

>>> li = ['a', 'b', 'c']
>>> li.extend(['d', 'e', 'f']) 
>>> li
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(li)                    
6
>>> li[-1]
'f'
>>> li = ['a', 'b', 'c']
>>> li.append(['d', 'e', 'f']) 
>>> li
['a', 'b', 'c', ['d', 'e', 'f']]
>>> len(li)                    
4
>>> li[-1]
['d', 'e', 'f'] 
[1]Lists 的两个方法 extendappend 看起来类似,但实际上完全不同。extend 接受一个参数,这个参数总是一个 list,并且把这个 list 中的每个元素添加到原 list 中。
[2]在这里 list 中有 3 个元素 ('a''b''c'),并且使用另一个有 3 个元素 ('d''e''f') 的 list 扩展之,因此新的 list 中有 6 个元素。
[3]另一方面,append 接受一个参数,这个参数可以是任何数据类型,并且简单地追加到 list 的尾部。在这里使用一个含有 3 个元素的 list 参数调用 append 方法。
[4]原来包含 3 个元素的 list 现在包含 4 个元素。为什么是 4 个元素呢?因为刚刚追加的最后一个元素本身是个 list。List 可以包含任何类型的数据,也包括其他的 list。这或许是您所要的结果,或许不是。如果您的意图是 extend,请不要使用 append

3.2.3. 在 list 中搜索

例 3.12. 搜索 list

>>> li
['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements']
>>> li.index("example") 
5
>>> li.index("new")     
2
>>> li.index("c")       
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
>>> "c" in li           
False 
[1]index 在 list 中查找一个值的首次出现并返回索引值。
[2]index 在 list 中查找一个值的首次 出现。这里 'new' 在 list 中出现了两次,在 li[2]li[6],但 index 只返回第一个索引,2
[3]如果在 list 中没有找到值,Python 会引发一个异常。这一点与大部分的语言截然不同,大部分语言会返回某个无效索引。尽管这种处理可能令人讨厌,但它仍然是件好事,因为它说明您的程序会由于源代码的问题而崩溃,好于在后面当您使用无效索引而引起崩溃。
[4]要测试一个值是否在 list 内,使用 in。如果值存在,它返回 True,否则返为 False

注意 在 2.2.1 版本之前,Python 没有单独的布尔数据类型。为了弥补这个缺陷,Python 在布尔环境 (如 if 语句) 中几乎接受所有东西,遵循下面的规则:

  • 0 为 false; 其它所有数值皆为 true。
  • 空串 ("") 为 false; 其它所有字符串皆为 true。
  • 空 list ([]) 为 false; 其它所有 list 皆为 true。
  • 空 tuple (()) 为 false; 其它所有 tuple 皆为 true。
  • 空 dictionary ({}) 为 false; 其它所有 dictionary 皆为 true。

这些规则仍然适用于 Python 2.2.1 及其后续版本,但现在您也可以使用真正的布尔值,它的值或者为 True 或者为 False。请注意第一个字母是大写的;这些值如同在 Python 中的其它东西一样都是大小写敏感的。

3.2.4. 从 list 中删除元素

例 3.13. 从 list 中删除元素

>>> li
['a', 'b', 'new', 'mpilgrim', 'z', 'example', 'new', 'two', 'elements']
>>> li.remove("z")   
>>> li
['a', 'b', 'new', 'mpilgrim', 'example', 'new', 'two', 'elements']
>>> li.remove("new") 
>>> li
['a', 'b', 'mpilgrim', 'example', 'new', 'two', 'elements']
>>> li.remove("c")   
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
ValueError: list.remove(x): x not in list
>>> li.pop()         
'elements'
>>> li
['a', 'b', 'mpilgrim', 'example', 'new', 'two'] 
[1]remove 从 list 中删除一个值的首次出现。
[2]remove 仅仅 删除一个值的首次出现。在这里,'new' 在 list 中出现了两次,但 li.remove("new") 只删除了 'new' 的首次出现。
[3]如果在 list 中没有找到值,Python 会引发一个异常来响应 index 方法。
[4]pop 是一个有趣的东西。它会做两件事:删除 list 的最后一个元素,然后返回删除元素的值。请注意,这与 li[-1] 不同,后者返回一个值但不改变 list 本身。也不同于 li.remove(_value_),后者改变 list 但并不返回值。

3.2.5. 使用 list 的运算符

例 3.14. List 运算符

>>> li = ['a', 'b', 'mpilgrim']
>>> li = li + ['example', 'new'] 
>>> li
['a', 'b', 'mpilgrim', 'example', 'new']
>>> li += ['two']                
>>> li
['a', 'b', 'mpilgrim', 'example', 'new', 'two']
>>> li = [1, 2] * 3              
>>> li
[1, 2, 1, 2, 1, 2] 
[1]Lists 也可以用 + 运算符连接起来。_list_ = _list_ + _otherlist_ 相当于 _list_.extend(_otherlist_)。但 + 运算符把一个新 (连接后) 的 list 作为值返回,而 extend 只修改存在的 list。也就是说,对于大型 list 来说,extend 的执行速度要快一些。
[2]Python 支持 += 运算符。li += ['two'] 等同于 li.extend(['two'])+= 运算符可用于 list、字符串和整数,并且它也可以被重载用于用户自定义的类中 (更多关于类的内容参见 第五章)。
[3]* 运算符可以作为一个重复器作用于 list。li = [1, 2] * 3 等同于 li = [1, 2] + [1, 2] + [1, 2],即将三个 list 连接成一个。

进一步阅读

3.3. Tuple 介绍

3.3. Tuple 介绍

Tuple 是不可变的 list。一旦创建了一个 tuple,就不能以任何方式改变它。

例 3.15. 定义 tuple

>>> t = ("a", "b", "mpilgrim", "z", "example") 
>>> t
('a', 'b', 'mpilgrim', 'z', 'example')
>>> t[0]                                       
'a'
>>> t[-1]                                      
'example'
>>> t[1:3]                                     
('b', 'mpilgrim') 
[1]定义 tuple 与定义 list 的方式相同,但整个元素集是用小括号包围的,而不是方括号。
[2]Tuple 的元素与 list 一样按定义的次序进行排序。Tuples 的索引与 list 一样从 0 开始,所以一个非空 tuple 的第一个元素总是 t[0]
[3]负数索引与 list 一样从 tuple 的尾部开始计数。
[4]与 list 一样分片 (slice) 也可以使用。注意当分割一个 list 时,会得到一个新的 list ;当分割一个 tuple 时,会得到一个新的 tuple。

例 3.16. Tuple 没有方法

>>> t
('a', 'b', 'mpilgrim', 'z', 'example')
>>> t.append("new")    
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> t.remove("z")      
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> t.index("example") 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'index'
>>> "z" in t           
True 
[1]您不能向 tuple 增加元素。Tuple 没有 appendextend 方法。
[2]您不能从 tuple 删除元素。Tuple 没有 removepop 方法。
[3]您不能在 tuple 中查找元素。Tuple 没有 index 方法。
[4]然而,您可以使用 in 来查看一个元素是否存在于 tuple 中。

那么使用 tuple 有什么好处呢?

  • Tuple 比 list 操作速度快。如果您定义了一个值的常量集,并且唯一要用它做的是不断地遍历它,请使用 tuple 代替 list。
  • 如果对不需要修改的数据进行 “写保护”,可以使代码更安全。使用 tuple 而不是 list 如同拥有一个隐含的 assert 语句,说明这一数据是常量。如果必须要改变这些值,则需要执行 tuple 到 list 的转换 (需要使用一个特殊的函数)。
  • 还记得我说过 dictionary keys 可以是字符串,整数和 “其它几种类型”吗?Tuples 就是这些类型之一。Tuples 可以在 dictionary 中被用做 key,但是 list 不行。实际上,事情要比这更复杂。Dictionary key 必须是不可变的。Tuple 本身是不可改变的,但是如果您有一个 list 的 tuple,那就认为是可变的了,用做 dictionary key 就是不安全的。只有字符串、整数或其它对 dictionary 安全的 tuple 才可以用作 dictionary key。
  • Tuples 可以用在字符串格式化中,我们会很快看到。

注意 Tuple 可以转换成 list,反之亦然。内置的 tuple 函数接收一个 list,并返回一个有着相同元素的 tuple。而 list 函数接收一个 tuple 返回一个 list。从效果上看,tuple 冻结一个 list,而 list 解冻一个 tuple。

进一步阅读

3.4. 变量声明

3.4. 变量声明

  • 3.4.1. 变量引用
  • 3.4.2. 一次赋多值

现在您已经了解了有关 dictionary、tuple、和 list 的相关知识 (哦,我的老天!),让我们回到 第二章 的例子程序 odbchelper.py

Python 与大多数其它语言一样有局部变量和全局变量之分,但是它没有明显的变量声明。变量通过首次赋值产生,当超出作用范围时自动消亡。

例 3.17. 定义 myParams 变量

 if __name__ == "__main__":
    myParams = {"server":"mpilgrim", \
                "database":"master", \
                "uid":"sa", \
                "pwd":"secret" \
                } 

首先注意缩进。if 语句是代码块,需要像函数一样缩进。

其次,变量的赋值是一条被分成了多行的命令,用反斜线 (“\”) 作为续行符。

注意 当一条命令用续行符 (“\”) 分割成多行时,后续的行可以以任何方式缩进,此时 Python 通常的严格的缩进规则无需遵守。如果您的 Python IDE 自由对后续行进行了缩进,您应该把它当成是缺省处理,除非您有特别的原因不这么做。

严格地讲,在小括号,方括号或大括号中的表达式 (如定义一个 dictionary) 可以用或者不用续行符 (“\”) 分割成多行。甚至在不是必需的时候,我也喜欢使用续行符,因为我认为这样会让代码读起来更容易,但那只是风格问题。

第三,您从未声明过变量 myParams,您只是给它赋了一个值。这点就像是 VBScript 没有设置 option explicit 选项一样。幸运的是,与 VBScript 不同,Python 不允许您引用一个未被赋值的变量,试图这样做会引发一个异常。

3.4.1. 变量引用

例 3.18. 引用未赋值的变量

>>> x
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
NameError: There is no variable named 'x'
>>> x = 1
>>> x
1 

迟早有一天您会为此而感谢 Python 。

3.4.2. 一次赋多值

Python 中比较 “酷” 的一种编程简写是使用序列来一次给多个变量赋值。

例 3.19. 一次赋多值

>>> v = ('a', 'b', 'e')
>>> (x, y, z) = v     
>>> x
'a'
>>> y
'b'
>>> z
'e' 
[1]v 是一个三元素的 tuple,并且 (x, y, z) 是一个三变量的 tuple。将一个 tuple 赋值给另一个 tuple,会按顺序将 v 的每个值赋值给每个变量。

这种用法有许多种用途。我经常想要将一定范围的值赋给多个变量。在 C 语言中,可以使用 enum 类型,手工列出每个常量和其所对应的值,当值是连续的时候这一过程让人感到特别繁琐。而在 Python 中,您可以使用内置的 range 函数和多变量赋值的方法来快速进行赋值。

例 3.20. 连续值赋值

>>> range(7)                                                                    
[0, 1, 2, 3, 4, 5, 6]
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7) 
>>> MONDAY                                                                      
0
>>> TUESDAY
1
>>> SUNDAY
6 
[1]内置的 range 函数返回一个元素为整数的 list。这个函数的简化调用形式是接收一个上限值,然后返回一个初始值从 0 开始的 list,它依次递增,直到但不包含上限值。(如果您愿意,您可以传入其它的参数来指定一个非 0 的初始值和非 1 的步长。也可以使用 print range.__doc__ 来了解更多的细节。)
[2]MONDAYTUESDAYWEDNESDAYTHURSDAYFRIDAYSATURDAYSUNDAY 是我们定义的变量。(这个例子来自 calendar 模块。它是一个很有趣的打印日历的小模块,像 UNIX 的 cal 命令。这个 calendar 模块定义了一星期中每天的整数常量表示。)
[3]现在每个变量都拥有了自己的值:MONDAY 的值为 0TUESDAY 的值为 1,等等。

您也可以使用多变量赋值来创建返回多个值的函数,只要返回一个包含所有值的 tuple 即可。调用者可以将其视为一个 tuple,或将值赋给独立的变量。许多标准的 Python 库都是这样做的,包括 os 模块,我们将在 第六章 中讨论。

进一步阅读

3.5. 格式化字符串

3.5. 格式化字符串

Python 支持格式化字符串的输出 。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。

注意 在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。

例 3.21. 字符串的格式化

>>> k = "uid"
>>> v = "sa"
>>> "%s=%s" % (k, v) 
'uid=sa' 
[1]整个表达式的值为一个字符串。第一个 %s 被变量 k 的值替换;第二个 %sv 的值替换。字符串中的所有其它字符 (在这个例子中,是等号) 按原样打印输出。

注意 (k, v) 是一个 tuple。我说过它们对某些东西有用。

您可能一直在想,做了这么多工作只不过是为了做简单的字符串连接。您想的不错,只不过字符串格式化不只是连接。它甚至不仅仅是格式化。它也是强制类型转换。

例 3.22. 字符串格式化与字符串连接的比较

>>> uid = "sa"
>>> pwd = "secret"
>>> print pwd + " is not a good password for " + uid      
secret is not a good password for sa
>>> print "%s is not a good password for %s" % (pwd, uid) 
secret is not a good password for sa
>>> userCount = 6
>>> print "Users connected: %d" % (userCount, )            
Users connected: 6
>>> print "Users connected: " + userCount                 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects 
[1]+ 是字符串连接操作符。
[2]在这个简单例子中,字符串格式化实现与连接一样的结果。
[3](userCount, ) 是一个只包含一个元素的 tuple。是的,语法有一点奇怪,但是使用它的理由就是:显示地指出它是一个 tuple,而不是其他。实际上,当定义一个 list、tuple 或 dictionary 时,您可以总是在最后一个元素后面跟上一个逗号,但是当定义一个只包含一个元素的 tuple 时逗号是必须的。如果省略逗号,Python 不会知道 (userCount) 究竟是一个只包含一个元素的 tuple 还是变量 userCount 的值。
[4]字符串格式化通过将 %s 替换成 %d 即可处理整数。
[5]试图将一个字符串同一个非字符串连接会引发一个异常。与字符串格式化不同,字符串连接只能在被连接的每一个都是字符串时起作用。

如同 printf 在 C 中的作用,Python 中的字符串格式化是一把瑞士军刀。它有丰富的选项,不同的格式化格式符和可选的修正符可用于不同的数据类型。

例 3.23. 数值的格式化

>>> print "Today's stock price: %f" % 50.4625   
50.462500
>>> print "Today's stock price: %.2f" % 50.4625 
50.46
>>> print "Change since yesterday: %+.2f" % 1.5 
+1.50 
[1]%f 格式符选项对应一个十进制浮点数,不指定精度时打印 6 位小数。
[2]使用包含“.2”精度修正符的 %f 格式符选项将只打印 2 位小数。
[3]您甚至可以混合使用各种修正符。添加 + 修正符用于在数值之前显示一个正号或负号。注意“.2”精度修正符仍旧在它原来的位置,用于只打印 2 位小数。

进一步阅读

3.6. 映射 list

3.6. 映射 list

Python 的强大特性之一是其对 list 的解析,它提供一种紧凑的方法,可以通过对 list 中的每个元素应用一个函数,从而将一个 list 映射为另一个 list。

例 3.24. List 解析介绍

>>> li = [1, 9, 8, 4]
>>> [elem*2 for elem in li]      
[2, 18, 16, 8]
>>> li                           
[1, 9, 8, 4]
>>> li = [elem*2 for elem in li] 
>>> li
[2, 18, 16, 8] 
[1]为了便于理解它,让我们从右向左看。li 是一个将要映射的 list。Python 循环遍历 li 中的每个元素。对每个元素均执行如下操作:首先临时将其值赋给变量 elem,然后 Python 应用函数 ``elem*2 进行计算,最后将计算结果追加到要返回的 list 中。
[2]需要注意是,对 list 的解析并不改变原始的 list。
[3]将一个 list 的解析结果赋值给对其映射的变量是安全的。不用担心存在竞争情况或任何古怪事情的发生。Python 会在内存中创建新的 list,当对 list 的解析完成时,Python 将结果赋给变量。

让我们回过头来看看位于 第二章 的函数 buildConnectionString 对 list 的解析:

["%s=%s" % (k, v) for k, v in params.items()] 

首先,注意到你调用了 dictionary paramsitems 函数。这个函数返回一个 dictionary 中所有数据的 tuple 的 list。

例 3.25. keys, valuesitems 函数

>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"}
>>> params.keys()   
['server', 'uid', 'database', 'pwd']
>>> params.values() 
['mpilgrim', 'sa', 'master', 'secret']
>>> params.items()  
[('server', 'mpilgrim'), ('uid', 'sa'), ('database', 'master'), ('pwd', 'secret')] 
[1]Dictionary 的 keys 方法返回一个包含所有键的 list。这个 list 没按 dictionary 定义的顺序输出 (记住,元素在 dictionary 中是无序的),但它是一个 list。
[2]values 方法返回一个包含所有值的 list。它同 keys 方法返回的 list 输出顺序相同,所以对于所有的 nparams.values()[n] == params[params.keys()[n]]
[3]items 方法返回一个由形如 (_key_,_value_) 组成的 tuple 的 list。这个 list 包括 dictionary 中所有的数据。

现在让我们看一看 buildConnectionString 做了些什么。它接收一个 list,params.items(),通过对每个元素应用字符串格式化将其映射为一个新 list。这个新 list 将与params.items() 一一对应:新 list 中的每个元素都是 dictionary params 中的一个键-值对构成的的字符串。

例 3.26. buildConnectionString 中的 list 解析

>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"}
>>> params.items()
[('server', 'mpilgrim'), ('uid', 'sa'), ('database', 'master'), ('pwd', 'secret')]
>>> [k for k, v in params.items()]                
['server', 'uid', 'database', 'pwd']
>>> [v for k, v in params.items()]                
['mpilgrim', 'sa', 'master', 'secret']
>>> ["%s=%s" % (k, v) for k, v in params.items()] 
['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret'] 
[1]请注意我们正在使用两个变量对 list params.items() 进行遍历。这是多变量赋值的另一种用法。params.items() 的第一个元素是 ('server', 'mpilgrim'),所以在 list 解析的第一次遍历中,k 将为 'server'v 将为 'mpilgrim'。在本例中,我们忽略了返回 list 中 v 的值,而只包含了 k 的值,所以这个 list 解析最后等于 ``params.keys()
[2]这里我们做着相同的事情,但是忽略了 k 的值,所以这个 list 解析最后等于 ``params.values()
[3]用一些简单的 字符串格式化将前面两个例子合并起来 ,我们就得到一个包括了 dictionary 中每个元素的 key-value 对的 list。这个看上去有点像程序的输出结果,剩下的就只是将这个 list 中的元素接起来形成一个字符串了。

进一步阅读

3.7. 连接 list 与分割字符串

3.7. 连接 list 与分割字符串

  • 3.7.1. 字符串方法的历史注解

您有了一个形如 _key_=_value_ 的 key-value 对 list,并且想将它们合成为单个字符串。为了将任意包含字符串的 list 连接成单个字符串,可以使用字符串对象的 join 方法。

下面是一个在 buildConnectionString 函数中连接 list 的例子:

 return ";".join(["%s=%s" % (k, v) for k, v in params.items()]) 

在我们继续之前有一个有趣的地方。我一直在重复函数是对象,字符串是对象,每个东西都是对象的概念。您也许认为我的意思是说字符串 是对象。但是不对,仔细地看一下这个例子,您将会看到字符串 ";" 本身就是一个对象,您在调用它的 join 方法。

总之,这里的 join 方法将 list 中的元素连接成单个字符串,每个元素用一个分号隔开。分隔符不必是一个分号;它甚至不必是单个字符。它可以是任何字符串。

小心 join 只能用于元素是字符串的 list;它不进行任何的强制类型转换。连接一个存在一个或多个非字符串元素的 list 将引发一个异常。

例 3.27. odbchelper.py 的输出结果

>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"}
>>> ["%s=%s" % (k, v) for k, v in params.items()]
['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret']
>>> ";".join(["%s=%s" % (k, v) for k, v in params.items()])
'server=mpilgrim;uid=sa;database=master;pwd=secret' 

上面的字符串是从 odbchelper 函数返回的,被调用块打印出来,这样就给出了您开始阅读本章时令人感到吃惊的输出结果。

您可能在想是否存在一个适当的方法来将字符串分割成一个 list。当然有,它叫做 split

例 3.28. 分割字符串

>>> li = ['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret']
>>> s = ";".join(li)
>>> s
'server=mpilgrim;uid=sa;database=master;pwd=secret'
>>> s.split(";")    
['server=mpilgrim', 'uid=sa', 'database=master', 'pwd=secret']
>>> s.split(";", 1) 
['server=mpilgrim', 'uid=sa;database=master;pwd=secret'] 
[1]splitjoin 正好相反,它将一个字符串分割成多元素 list。注意,分隔符 (“;”) 被完全去掉了,它没有在返回的 list 中的任意元素中出现。
[2]split 接受一个可选的第二个参数,它是要分割的次数。(“哦,可选参数……”,您将会在下一章中学会如何在您自己的函数中使用它。)

提示 _anystring_.split(_delimiter_, 1) 是一个有用的技术,在您想要搜索一个子串,然后分别处理字符前半部分 (即 list 中第一个元素) 和后半部分 (即 list 中第二个元素) 时,使用这个技术。

进一步阅读

3.7.1. 字符串方法的历史注解

当我开始学 Python 时,我以为 join 是 list 的方法,它会使用分隔符作为一个参数。很多人都有同样的感觉:在 join 方法的背后有一段故事。在 Python 1.6 之前,字符串完全没有这些有用的方法。有一个独立的 string 模块包含所有的字符串函数,每个函数使用一个字符串作为它的第一个参数。这些函数被认为足够重要,所以它们移到字符串中去了,这就使得诸如 loweruppersplit 之类的函数是有意义的。但许多核心的 Python 程序员反对新的 join 方法,争论说应该换成是 list 的一个方法,或不应该移动而仅仅保留为旧的 string 模块 (现仍然还有许多有用的东西在里面) 的一部分。我只使用新的 join 方法,但是您还是会看到其它写法。如果它真的使您感到麻烦,您可以使用旧的 string.join 函数来替代。

3.8. 小结

3.8. 小结

现在 odbchelper.py 程序和它的输出结果都应该非常清楚了。

 def buildConnectionString(params):
    """Build a connection string from a dictionary of parameters.
    Returns string."""
    return ";".join(["%s=%s" % (k, v) for k, v in params.items()])
if __name__ == "__main__":
    myParams = {"server":"mpilgrim", \
                "database":"master", \
                "uid":"sa", \
                "pwd":"secret" \
                }
    print buildConnectionString(myParams) 

下面是 odbchelper.py 的输出结果:

server=mpilgrim;uid=sa;database=master;pwd=secret 

在深入下一章学习之前,确保您可以无阻碍地完成下面的事情:

  • 使用 Python IDE 来交互式地测试表达式
  • 编写 Python 程序并且从 IDE 运行,或者从命令行运行
  • 导入模块及调用它们的函数
  • 声明函数以及 doc string、局部变量和适当的缩进的使用
  • 定义 dictionary、tuple 和 list
  • 任意一个对象的访问方法,包括:字符串、list、dictionary、函数和模块
  • 通过字符串格式化连接值
  • 使用 list 解析映射 list 为其他的 list
  • 把字符串分割为 list 和把 list 连接为字符串

第四章 自省的威力

第四章 自省的威力

  • 4.1. 概览
  • 4.2. 使用可选参数和命名参数
  • 4.3. 使用 type、str、dir 和其它内置函数
    • 4.3.1. type 函数
    • 4.3.2. str 函数
    • 4.3.3. 内置函数
  • 4.4. 通过 getattr 获取对象引用
    • 4.4.1. 用于模块的 getattr
    • 4.4.2. getattr 作为一个分发者
  • 4.5. 过滤列表
  • 4.6. and 和 or 的特殊性质
    • 4.6.1. 使用 and-or 技巧
  • 4.7. 使用 lambda 函数
    • 4.7.1. 真实世界中的 lambda 函数
  • 4.8. 全部放在一起
  • 4.9. 小结

本章论述了 Python 众多强大功能之一:自省。正如你所知道的,Python 中万物皆对象,自省是指代码可以查看内存中以对象形式存在的其它模块和函数,获取它们的信息,并对它们进行操作。用这种方法,你可以定义没有名称的函数,不按函数声明的参数顺序调用函数,甚至引用事先并不知道名称的函数。

4.1. 概览

4.1. 概览

下面是一个完整可运行的 Python 程序。大概看一下这段程序,你应该可以理解不少了。用数字标出的行阐述了 第二章 第一个 Python 程序 中涉及的一些概念。如果剩下来的代码看起来有点奇怪,不用担心,通过阅读本章你将会理解所有这些。

例 4.1. apihelper.py

如果您还没有下载本书附带的样例程序, 可以 下载本程序和其他样例程序

 def info(object, spacing=10, collapse=1):   
    """Print methods and doc strings.
    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])
if __name__ == "__main__":                 
    print info.__doc__ 
[1]该模块有一个声明为 info 的函数。根据它的函数声明可知,它有三个参数: objectspacingcollapse。实际上后面两个参数都是可选参数,关于这点你很快就会看到。
[2]info 函数有一个多行的 doc string,简要地描述了函数的功能。注意这里并没有提到返回值;单独使用这个函数只是为了这个函数产生的效果,并不是为了它的返回值。
[3]函数内的代码是缩进形式的。
[4]if __name__ 技巧允许这个程序在自己独立运行时做些有用的事情,同时又不妨碍作为其它程序的模块使用。在这个例子中,程序只是简单地打印出 info 函数的 doc string
[5]if 语句使用 == 进行比较,而且不需要括号。

info 函数的设计意图是提供给工作在 Python IDE 中的开发人员使用,它可以接受任何含有函数或者方法的对象 (比如模块,含有函数,又比如 list,含有方法) 作为参数,并打印出对象的所有函数和它们的 doc string

例 4.2. apihelper.py 的用法示例

>>> from apihelper import info
>>> li = []
>>> info(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1 

缺省地,程序输出进行了格式化处理,以使其易于阅读。多行 doc string 被合并到单行中,要改变这个选项需要指定 collapse 参数的值为 0。如果函数名称长于 10 个字符,你可以将 spacing 参数的值指定为更大的值以使输出更容易阅读。

例 4.3. apihelper.py 的高级用法

>>> import odbchelper
>>> info(odbchelper)
buildConnectionString Build a connection string from a dictionary Returns string.
>>> info(odbchelper, 30)
buildConnectionString          Build a connection string from a dictionary Returns string.
>>> info(odbchelper, 30, 0)
buildConnectionString          Build a connection string from a dictionary
    Returns string. 

4.2. 使用可选参数和命名参数

4.2. 使用可选参数和命名参数

Python 允许函数参数有缺省值;如果调用函数时不使用参数,参数将获得它的缺省值。此外,通过使用命名参数还可以以任意顺序指定参数。SQL Server Transact/SQL 中的存储过程也可以做到这些;如果你是脚本高手,你可以略过这部分。

info 函数就是这样一个例子,它有两个可选参数。

 def info(object, spacing=10, collapse=1): 

spacingcollapse 是可选参数,因为它们已经定义了缺省值。object 是必备参数,因为它没有指定缺省值。如果调用 info 时只指定一个参数,那么 spacing 缺省为 10collapse 缺省为 1。如果调用 info 时指定两个参数,collapse 依然默认为 1

假如你要指定 collapse 的值,但是又想要接受 spacing 的缺省值。在绝大部分语言中,你可能运气就不太好了,因为你需要使用三个参数来调用函数,这势必要重新指定 spacing 的值。但是在 Python 中,参数可以通过名称以任意顺序指定。

例 4.4. info 的有效调用

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper) 
[1]只使用一个参数,spacing 使用缺省值 10collapse 使用缺省值 1
[2]使用两个参数,collapse 使用缺省值 1
[3]这里你显式命名了 collapse 并指定了它的值。spacing 将依然使用它的缺省值 10
[4]甚至必备参数 (例如 object,没有指定缺省值) 也可以采用命名参数的方式,而且命名参数可以以任意顺序出现。

这些看上去非常累,除非你意识到参数不过是一个字典。“通常” 不使用参数名称的函数调用只是一个简写的形式,Python 按照函数声明中定义的的参数顺序将参数值和参数名称匹配起来。大部分时间,你会使用“通常”方式调用函数,但是如果你需要,总是可以提供附加的灵活性。

注意 调用函数时唯一必须做的事情就是为每一个必备参数指定值 (以某种方式);以何种具体的方式和顺序都取决于你。

进一步阅读

4.3. 使用 typestrdir 和其它内置函数

4.3. 使用 typestrdir 和其它内置函数

  • 4.3.1. type 函数
  • 4.3.2. str 函数
  • 4.3.3. 内置函数

Python 有小部分相当有用的内置函数。除这些函数之外,其它所有的函数都被分到了各个模块中。其实这是一个非常明智的设计策略,避免了核心语言变得像其它脚本语言一样臃肿 (咳 咳,Visual Basic)。

4.3.1. type 函数

type 函数返回任意对象的数据类型。在 types 模块中列出了可能的数据类型。这对于处理多种数据类型的帮助者函数 [1] 非常有用。

例 4.5. type 介绍

>>> type(1)           
<type 'int'>
>>> li = []
>>> type(li)          
<type 'list'>
>>> import odbchelper
>>> type(odbchelper)  
<type 'module'>
>>> import types      
>>> type(odbchelper) == types.ModuleType
True 
[1]type 可以接收任何东西作为参数――我的意思是任何东西――并返回它的数据类型。整型、字符串、列表、字典、元组、函数、类、模块,甚至类型对象都可以作为参数被 type 函数接受。
[2]type 可以接收变量作为参数,并返回它的数据类型。
[3]type 还可以作用于模块。
[4]你可以使用 types 模块中的常量来进行对象类型的比较。这就是 info 函数所做的,很快你就会看到。

4.3.2. str 函数

str 将数据强制转换为字符串。每种数据类型都可以强制转换为字符串。

例 4.6. str 介绍

>>> str(1)          
'1'
>>> horsemen = ['war', 'pestilence', 'famine']
>>> horsemen
['war', 'pestilence', 'famine']
>>> horsemen.append('Powerbuilder')
>>> str(horsemen)   
"['war', 'pestilence', 'famine', 'Powerbuilder']"
>>> str(odbchelper) 
"<module 'odbchelper' from 'c:\\docbook\\dip\\py\\odbchelper.py'>"
>>> str(None)       
'None' 
[1]对于简单的数据类型比如整型,你可以预料到 str 的正常工作,因为几乎每种语言都有一个将整型转化为字符串的函数。
[2]然而 str 可以作用于任何数据类型的任何对象。这里它作用于一个零碎构建的列表。
[3]str 还允许作用于模块。注意模块的字符串形式表示包含了模块在磁盘上的路径名,所以你的显示结果将会有所不同。
[4]str 的一个细小但重要的行为是它可以作用于 NoneNone 是 Python 的 null 值。这个调用返回字符串 'None'。你将会使用这一点来改进你的 info 函数,这一点你很快就会看到。

info 函数的核心是强大的 dir 函数。dir 函数返回任意对象的属性和方法列表,包括模块对象、函数对象、字符串对象、列表对象、字典对象 …… 相当多的东西。

例 4.7. dir 介绍

>>> li = []
>>> dir(li)           
['append', 'count', 'extend', 'index', 'insert',
'pop', 'remove', 'reverse', 'sort']
>>> d = {}
>>> dir(d)            
['clear', 'copy', 'get', 'has_key', 'items', 'keys', 'setdefault', 'update', 'values']
>>> import odbchelper
>>> dir(odbchelper)   
['__builtins__', '__doc__', '__file__', '__name__', 'buildConnectionString'] 
[1]li 是一个列表,所以 ``dir(li) 返回一个包含所有列表方法的列表。注意返回的列表只包含了字符串形式的方法名称,而不是方法对象本身。
[2]d 是一个字典,所以 ``dir(d)返回字典方法的名称列表。其中至少有一个方法,keys,看起来还是挺熟悉的。
[3]这里就是真正变得有趣的地方。odbchelper 是一个模块,所以 ``dir(odbchelper)返回模块中定义的所有部件的列表,包括内置的属性,例如 **name****doc**,以及其它你所定义的属性和方法。在这个例子中,odbchelper只有一个用户定义的方法,就是在第二章中论述的buildConnectionString 函数。

最后是 callable 函数,它接收任何对象作为参数,如果参数对象是可调用的,返回 True;否则返回 False。可调用对象包括函数、类方法,甚至类自身 (下一章将更多的关注类)。

例 4.8. callable 介绍

>>> import string
>>> string.punctuation           
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
>>> string.join                  
<function join at 00C55A7C>
>>> callable(string.punctuation) 
False
>>> callable(string.join)        
True
>>> print string.join.__doc__    
join(list [,sep]) -> string
    Return a string composed of the words in list, with
    intervening occurrences of sep.  The default separator is a
    single space.
    (joinfields and join are synonymous) 
[1]string 模块中的函数现在已经不赞成使用了 (尽管很多人现在仍然还在使用 join 函数),但是在这个模块中包含了许多有用的变量,例如 string.punctuation,这个字符串包含了所有标准的标点符号字符。
[2]string.join 是一个用于连接字符串列表的函数。
[3]string.punctuation 是不可调用的对象;它是一个字符串。(字符串确有可调用的方法,但是字符串本身不是可调用的。)
[4]string.join 是可调用的;这个函数可以接受两个参数。
[5]任何可调用的对象都有 doc string。通过将 callable 函数作用于一个对象的每个属性,可以确定哪些属性 (方法、函数、类) 是你要关注的,哪些属性 (常量等等) 是你可以忽略、之前不需要知道的。

4.3.3. 内置函数

typestrdir 和其它的 Python 内置函数都归组到了 __builtin__ (前后分别是双下划线) 这个特殊的模块中。如果有帮助的话,你可以认为 Python 在启动时自动执行了 from __builtin__ import *,此语句将所有的 “内置” 函数导入该命名空间,所以在这个命名空间中可以直接使用这些内置函数。

像这样考虑的好处是,你是可以获取 __builtin__ 模块信息的,并以组的形式访问所有的内置函数和属性。猜到什么了吗,现在我们的 Python 有一个称为 info 的函数。自己尝试一下,略看一下结果列表。后面我们将深入到一些更重要的函数。(一些内置的错误类,比如 AttributeError,应该看上去已经很熟悉了。)

例 4.9. 内置属性和内置函数

>>> from apihelper import info
>>> import __builtin__
>>> info(__builtin__, 20)
ArithmeticError      Base class for arithmetic errors.
AssertionError       Assertion failed.
AttributeError       Attribute not found.
EOFError             Read beyond end of file.
EnvironmentError     Base class for I/O related errors.
Exception            Common base class for all exceptions.
FloatingPointError   Floating point operation failed.
IOError              I/O operation failed.
[...snip...] 

注意 Python 提供了很多出色的参考手册,你应该好好地精读一下所有 Python 提供的必备模块。对于其它大部分语言,你会发现自己要常常回头参考手册或者 man 页来提醒自己如何使用这些模块,但是 Python 不同于此,它很大程度上是自文档化的。

进一步阅读

Footnotes

[1] 帮助者函数,原文是 helper function,也就是我们在前文所看到的诸如 odbchelperapihelper 这样的函数。――译注

4.4. 通过 getattr 获取对象引用

4.4. 通过 getattr 获取对象引用

  • 4.4.1. 用于模块的 getattr
  • 4.4.2. getattr 作为一个分发者

你已经知道 Python 函数是对象。你不知道的是,使用 getattr 函数,可以得到一个直到运行时才知道名称的函数的引用。

例 4.10. getattr 介绍

>>> li = ["Larry", "Curly"]
>>> li.pop                       
<built-in method pop of list object at 010DF884>
>>> getattr(li, "pop")           
<built-in method pop of list object at 010DF884>
>>> getattr(li, "append")("Moe") 
>>> li
["Larry", "Curly", "Moe"]
>>> getattr({}, "clear")         
<built-in method clear of dictionary object at 00F113D4>
>>> getattr((), "pop")           
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'pop' 
[1]该语句获取列表的 pop 方法的引用。注意该语句并不是调用 pop 方法;调用 pop 方法的应该是 li.pop()。这里指的是方法对象本身。
[2]该语句也是返回 pop 方法的引用,但是此时,方法名称是作为一个字符串参数传递给 getattr 函数的。getattr 是一个有用到令人无法致信的内置函数,可以返回任何对象的任何属性。在这个例子中,对象是一个 list,属性是 pop 方法。
[3]如果不确信它是多么的有用,试试这个:getattr 的返回值 方法,然后你就可以调用它,就像直接使用 li.append("Moe") 一样。但是实际上你没有直接调用函数;只是以字符串形式指定了函数名称。
[4]getattr 也可以作用于字典。
[5]理论上,getattr 可以作用于元组,但是由于元组没有方法,所以不管你指定什么属性名称 getattr 都会引发一个异常。

4.4.1. 用于模块的 getattr

getattr 不仅仅适用于内置数据类型,也可作用于模块。

例 4.11. apihelper.py 中的 getattr 函数

>>> import odbchelper
>>> odbchelper.buildConnectionString             
<function buildConnectionString at 00D18DD4>
>>> getattr(odbchelper, "buildConnectionString") 
<function buildConnectionString at 00D18DD4>
>>> object = odbchelper
>>> method = "buildConnectionString"
>>> getattr(object, method)                      
<function buildConnectionString at 00D18DD4>
>>> type(getattr(object, method))                
<type 'function'>
>>> import types
>>> type(getattr(object, method)) == types.FunctionType
True
>>> callable(getattr(object, method))            
True 
[1]该语句返回 odbchelper 模块中 buildConnectionString 函数的引用,第二章 第一个 Python 程序 你已经研习过这个方法了。(你看到的这个十六进制地址是我机器上的;你的输出结果会有所不同。)
[2]使用 getattr,你能够获得同一函数的同一引用。通常,``getattr(*object*, "*attribute*")等价于*object*.*attribute*。如果 object 是一个模块的话,那么 attribute 可能是定义在模块中的任何东西:函数、类或者全局变量。
[3]接下来的是你真正用在 info 函数中的东西。object 作为一个参数传递给函数; method 是方法或者函数的名称字符串。
[4]在这个例子中,method 是函数的名称,通过获取 type 可以进行验证。
[5]由于 method 是一个函数,所以它是可调用的。

4.4.2. getattr 作为一个分发者

getattr 常见的使用模式是作为一个分发者。举个例子,如果你有一个程序可以以不同的格式输出数据,你可以为每种输出格式定义各自的格式输出函数,然后使用唯一的分发函数调用所需的格式输出函数。

例如,让我们假设有一个以 HTML、XML 和普通文本格式打印站点统计的程序。输出格式在命令行中指定,或者保存在配置文件中。statsout 模块定义了三个函数:output_htmloutput_xmloutput_text。然后主程序定义了唯一的输出函数,如下:

例 4.12. 使用getattr 创建分发者

 import statsout
def output(data, format="text"):                              
    output_function = getattr(statsout, "output_%s" % format) 
    return output_function(data) 
[1]output 函数接收一个必备参数 data,和一个可选参数 format。如果没有指定 format 参数,其缺省值是 text 并完成普通文本输出函数的调用。
[2]你可以连接 format 参数值和 "output_" 来创建一个函数名称作为参数值,然后从 statsout 模块中取得该函数。这种方式允许今后很容易地扩展程序以支持其它的输出格式,而且无需修改分发函数。所要做的仅仅是向 statsout 中添加一个函数,比如 output_pdf,之后只要将 “pdf” 作为 format 的参数值传递给 output 函数即可。
[3]现在你可以简单地调用输出函数,就像调用其它函数一样。output_function 变量是指向 statsout 模块中相应函数的引用。

你是否发现前面示例的一个 Bug?即字符串和函数之间的松耦合,而且没有错误检查。如果用户传入一个格式参数,但是在 statsout 中没有定义相应的格式输出函数,会发生什么呢?还好,getattr 会返回 None,它会取代一个有效函数并被赋值给 output_function,然后下一行调用函数的语句将会失败并抛出一个异常。这种方式不好。

值得庆幸的是,getattr 能够使用可选的第三个参数,一个缺省返回值。

例 4.13. getattr 缺省值

 import statsout
def output(data, format="text"):
    output_function = getattr(statsout, "output_%s" % format, statsout.output_text)
    return output_function(data) 
[1]这个函数调用一定可以工作,因为你在调用 getattr 时添加了第三个参数。第三个参数是一个缺省返回值,如果第二个参数指定的属性或者方法没能找到,则将返回这个缺省返回值。

正如你所看到,getattr 是相当强大的。它是自省的核心,在后面的章节中你将看到它更强大的示例。

4.5. 过滤列表

4.5. 过滤列表

如你所知,Python 具有通过列表解析 (第 3.6 节 “映射 list”) 将列表映射到其它列表的强大能力。这种能力同过滤机制结合使用,使列表中的有些元素被映射的同时跳过另外一些元素。

过滤列表语法:

[`mapping-expression` for `element` in `source-list` if `filter-expression`] 

这是你所知所爱的列表解析的扩展。前三部分都是相同的;最后一部分,以 if 开头的是过滤器表达式。过滤器表达式可以是返回值为真或者假的任何表达式 (在 Python 中是几乎任何东西)。任何经过滤器表达式演算值为真的元素都可以包含在映射中。其它的元素都将忽略,它们不会进入映射表达式,更不会包含在输出列表中。

例 4.14. 列表过滤介绍

>>> li = ["a", "mpilgrim", "foo", "b", "c", "b", "d", "d"]
>>> [elem for elem in li if len(elem) > 1]       
['mpilgrim', 'foo']
>>> [elem for elem in li if elem != "b"]         
['a', 'mpilgrim', 'foo', 'c', 'd', 'd']
>>> [elem for elem in li if li.count(elem) == 1] 
['a', 'mpilgrim', 'foo', 'c'] 
[1]这里的映射表达式很简单 (只是返回每个元素的值),所以请把注意力集中到过滤器表达式上。由于 Python 会遍历整个列表,它将对每个元素执行过滤器表达式。如果过滤器表达式演算值为真,该元素就会被映射,同时映射表达式的结果将包含在返回的列表中。这里,你过滤掉了所有单字符的字符串,留下了一个由长字符串构成的列表。
[2]这里你过滤掉了一个特定值 b。注意这个过滤器会过滤掉所有的 b,因为每次取出 b,过滤表达式都将为假。
[3]count 是一个列表方法,返回某个值在列表中出现的次数。你可以认为这个过滤器将从列表中剔除重复元素,返回一个只包含了在原始列表中有着唯一值拷贝的列表。但并非如此,因为在原始列表中出现两次的值 (在本例中,bd) 被完全剔除了。从一个列表中排除重复值有多种方法,但过滤并不是其中的一种。

回到 apihelper.py 中的这一行:

 methodList = [method for method in dir(object) if callable(getattr(object, method))] 

这行看上去挺复杂――确实也很复杂――但是基本结构都还是一样的。整个过滤表达式返回一个列表,并赋值给 methodList 变量。表达式的前半部分是列表映射部分。映射表达式是一个和遍历元素相同的表达式,因此它返回每个元素的值。``dir(object)返回object对象的属性和方法列表――你正在映射的列表。所以唯一新出现的部分就是在if 后面的过滤表达式。

过滤表达式看上去很恐怖,其实不是。你已经知道了 callablegetattrin。正如你在前面的部分中看到的,如果 object 是一个模块,并且 method 是上述模块中某个函数的名称,那么表达式 getattr(object, method) 将返回一个函数对象。

所以这个表达式接收一个名为 object 的对象,然后得到它的属性、方法、函数和其他成员的名称列表,接着过滤掉我们不关心的成员。执行过滤行为是通过对每个属性/方法/函数的名称调用 getattr 函数取得实际成员的引用,然后检查这些成员对象是否是可调用的,当然这些可调用的成员对象可能是方法或者函数,同时也可能是内置的 (比如列表的 pop 方法) 或者用户自定义的 (比如 odbchelper 模块的 buildConnectionString 函数)。这里你不用关心其它的属性,如内置在每一个模块中的 __name__ 属性。

进一步阅读

4.6. andor 的特殊性质

4.6. andor 的特殊性质

  • 4.6.1. 使用 and-or 技巧

在 Python 中,andor 执行布尔逻辑演算,如你所期待的一样。但是它们并不返回布尔值,而是返回它们实际进行比较的值之一。

例 4.15. and 介绍

>>> 'a' and 'b'         
'b'
>>> '' and 'b'          
''
>>> 'a' and 'b' and 'c' 
'c' 
[1]使用 and 时,在布尔环境中从左到右演算表达式的值。0''[](){}None 在布尔环境中为假;其它任何东西都为真。还好,几乎是所有东西。默认情况下,布尔环境中的类实例为真,但是你可以在类中定义特定的方法使得类实例的演算值为假。你将会在第五章中了解到类和这些特殊方法。如果布尔环境中的所有值都为真,那么 and 返回最后一个值。在这个例子中,and 演算 'a' 的值为真,然后是 'b' 的演算值为真,最终返回 'b'
[2]如果布尔环境中的某个值为假,则 and 返回第一个假值。在这个例子中,'' 是第一个假值。
[3]所有值都为真,所以 and 返回最后一个真值,'c'

例 4.16. or 介绍

>>> 'a' or 'b'          
'a'
>>> '' or 'b'           
'b'
>>> '' or [] or {}      
{}
>>> def sidefx():
... print "in sidefx()"
... return 1
>>> 'a' or sidefx()     
'a' 
[1]使用 or 时,在布尔环境中从左到右演算值,就像 and 一样。如果有一个值为真,or 立刻返回该值。本例中,'a' 是第一个真值。
[2]or 演算 '' 的值为假,然后演算 'b' 的值为真,于是返回 'b'
[3]如果所有的值都为假,or 返回最后一个假值。or 演算 '' 的值为假,然后演算 [] 的值为假,依次演算 {} 的值为假,最终返回 {}
[4]注意 or 在布尔环境中会一直进行表达式演算直到找到第一个真值,然后就会忽略剩余的比较值。如果某些值具有副作用,这种特性就非常重要了。在这里,函数 sidefx 永远都不会被调用,因为 or 演算 'a' 的值为真,所以紧接着就立刻返回 'a' 了。

如果你是一名 C 语言黑客,肯定很熟悉 _bool_ ?a:b`` 表达式,如果 bool 为真,表达式演算值为a,否则为b。基于 Python 中andor 的工作方式,你可以完成相同的事情。

4.6.1. 使用 and-or 技巧

例 4.17. and-or 技巧介绍

>>> a = "first"
>>> b = "second"
>>> 1 and a or b 
'first'
>>> 0 and a or b 
'second' 
[1]这个语法看起来类似于 C 语言中的 _bool_ ?a:b`` 表达式。整个表达式从左到右进行演算,所以先进行and表达式的演算。1 and 'first'演算值为'first',然后'first' or 'second'的演算值为'first'
[2]0 and 'first' 演算值为 False,然后 0 or 'second' 演算值为 'second'

然而,由于这种 Python 表达式单单只是进行布尔逻辑运算,并不是语言的特定构成,这是 and-or 技巧和 C 语言中的 _bool_ ?a:b`` 语法非常重要的不同。如果a 为假,表达式就不会按你期望的那样工作了。(你能知道我被这个问题折腾过吗?不止一次?)

例 4.18. and-or 技巧无效的场合

>>> a = ""
>>> b = "second"
>>> 1 and a or b         
'second' 
[1]由于 a 是一个空字符串,在 Python 的布尔环境中空字符串被认为是假的,1 and '' 的演算值为 '',最后 '' or 'second' 的演算值为 'second'。噢!这个值并不是你想要的。

and-or 技巧,也就是 _bool_ andaorb表达式,当 a在布尔环境中的值为假时,不会像 C 语言表达式bool ? a : b 那样工作。

and-or 技巧后面真正的技巧是,确保 a 的值决不会为假。最常用的方式是使 a 成为 [a]b 成为 [b],然后使用返回值列表的第一个元素,应该是 ab中的某一个。

例 4.19. 安全使用 and-or 技巧

>>> a = ""
>>> b = "second"
>>> (1 and [a] or [b])[0] 
'' 
[1]由于 [a] 是一个非空列表,所以它决不会为假。即使 a0 或者 '' 或者其它假值,列表 [a] 也为真,因为它有一个元素。

到现在为止,这个技巧可能看上去问题超过了它的价值。毕竟,使用 if 语句可以完成相同的事情,那为什么要经历这些麻烦事呢?哦,在很多情况下,你要在两个常量值中进行选择,由于你知道 a 的值总是为真,所以你可以使用这种较为简单的语法而且不用担心。对于使用更为复杂的安全形式,依然有很好的理由要求这样做。例如,在 Python 语言的某些情况下 if 语句是不允许使用的,比如在 lambda 函数中。

进一步阅读

4.7. 使用 lambda 函数

4.7. 使用 lambda 函数

  • 4.7.1. 真实世界中的 lambda 函数

Python 支持一种有趣的语法,它允许你快速定义单行的最小函数。这些叫做 lambda 的函数,是从 Lisp 借用来的,可以用在任何需要函数的地方。

例 4.20. lambda 函数介绍

>>> def f(x):
... return x*2
... 
>>> f(3)
6
>>> g = lambda x: x*2  
>>> g(3)
6
>>> (lambda x: x*2)(3) 
6 
[1]这是一个 lambda 函数,完成同上面普通函数相同的事情。注意这里的简短的语法:在参数列表周围没有括号,而且忽略了 return 关键字 (隐含存在,因为整个函数只有一行)。而且,该函数没有函数名称,但是可以将它赋值给一个变量进行调用。
[2]使用 lambda 函数时甚至不需要将它赋值给一个变量。这可能不是世上最有用的东西,它只是展示了 lambda 函数只是一个内联函数。

总的来说,lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。lambda 函数不能包含命令,包含的表达式不能超过一个。不要试图向 lambda 函数中塞入太多的东西;如果你需要更复杂的东西,应该定义一个普通函数,然后想让它多长就多长。

注意 lambda 函数是一种风格问题。不一定非要使用它们;任何能够使用它们的地方,都可以定义一个单独的普通函数来进行替换。我将它们用在需要封装特殊的、非重用代码上,避免令我的代码充斥着大量单行函数。

4.7.1. 真实世界中的 lambda 函数

apihelper.py 中的 lambda 函数:

 processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) 

注意这里使用了 and-or 技巧的简单形式,它是没问题的,因为 lambda 函数在布尔环境中总是为真。(这并不意味这 lambda 函数不能返回假值。这个函数对象的布尔值为真;它的返回值可以是任何东西。)

还要注意的是使用了没有参数的 split 函数。你已经看到过它带一个或者两个参数的使用,但是不带参数它按空白进行分割。

例 4.21. split 不带参数

>>> s = "this   is\na\ttest"  
>>> print s
this   is
a    test
>>> print s.split()           
['this', 'is', 'a', 'test']
>>> print " ".join(s.split()) 
'this is a test' 
[1]这是一个多行字符串,通过使用转义字符的定义代替了三重引号。\n 是一个回车,\t 是一个制表符。
[2]不带参数的 split 按照空白进行分割。所以三个空格、一个回车和一个制表符都是一样的。
[3]通过 split 分割字符串你可以将空格统一化;然后再以单个空格作为分隔符用 join 将其重新连接起来。这也就是 info 函数将多行 doc string 合并成单行所做的事情。

那么 info 函数到底用这些 lambda 函数、split 函数和 and-or 技巧做了些什么呢?

 processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s) 

processFunc 现在是一个函数,但是它到底是哪一个函数还要取决于 collapse 变量。如果 collapse 为真,processFunc(string) 将压缩空白;否则processFunc(_string_) 将返回未改变的参数。

在一个不很健壮的语言中实现它,像 Visual Basic,你很有可能要创建一个函数,接受一个字符串参数和一个 collapse 参数,并使用 if 语句确定是否压缩空白,然后再返回相应的值。这种方式是低效的,因为函数可能需要处理每一种可能的情况。每次你调用它,它将不得不在给出你所想要的东西之前,判断是否要压缩空白。在 Python 中,你可以将决策逻辑拿到函数外面,而定义一个裁减过的 lambda 函数提供确切的 (唯一的) 你想要的。这种方式更为高效、更为优雅,而且很少引起那些令人讨厌 (哦,想到那些参数就头昏) 的错误。

lambda 函数进一步阅读

4.8. 全部放在一起

4.8. 全部放在一起

最后一行代码是唯一还没有解释过的,它完成全部的工作。但是现在工作已经简单了,因为所需要的每件事都已经按照需求建立好了。所有的多米诺骨牌已经就位,到了将它们推倒的时候了。

下面是 apihelper.py 的关键

 print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList]) 

注意这是一条命令,被分隔成了多行,但是并没有使用续行符 (\)。还记得我说过一些表达式可以分割成多行而不需要使用反斜线吗?列表解析就是这些表达式之一,因为整个表达式包括在方括号里。

现在,让我们从后向前分析。

 for method in methodList 

告诉我们这是一个列表解析。如你所知 methodListobject 中所有你关心的方法的一个列表。所以你正在使用 method 遍历列表。

例 4.22. 动态得到 doc string

>>> import odbchelper
>>> object = odbchelper                   
>>> method = 'buildConnectionString'      
>>> getattr(object, method)               
<function buildConnectionString at 010D6D74>
>>> print getattr(object, method).__doc__ 
Build a connection string from a dictionary of parameters.
    Returns string. 
[1]info 函数中,object 是要得到帮助的对象,作为一个参数传入。
[2]在你遍历 methodList 时,method 是当前方法的名称。
[3]通过 getattr 函数,你可以得到 object 模块中 method 函数的引用。
[4]现在,很容易就可以打印出方法的 doc string

接下来令人困惑的是 doc string 周围 str 的使用。你可能记得,str 是一个内置函数,它可以强制将数据转化为字符串。但是一个 doc string 应该总是一个字符串,为什么还要费事地使用 str 函数呢?答案就是:不是每个函数都有 doc string ,如果没有,这个 __doc__ 属性为 None

例 4.23. 为什么对一个 doc string 使用 str

>>> >>> def foo(): print 2
>>> >>> foo()
2
>>> >>> foo.__doc__     
>>> foo.__doc__ == None 
True
>>> str(foo.__doc__)    
'None' 
[1]你可以很容易的定义一个没有 doc string 的函数,这种情况下它的 __doc__ 属性为 None。令人迷惑的是,如果你直接演算 __doc__ 属性的值,Python IDE 什么都不会打印。这是有意义的 (前提是你考虑了这个结果的来由),但是却没有什么用。
[2]你可以直接通过 __doc__ 属性和 None 的比较验证 __doc__ 属性的值。
[3]str 函数可以接收值为 null 的参数,然后返回它的字符串表示,'None'

注意 在 SQL 中,你必须使用 IS NULL 代替 = NULL 进行 null 值比较。在 Python,你可以使用 == None 或者 is None 进行比较,但是 is None 更快。

现在你确保有了一个字符串,可以把这个字符串传给 processFunc,这个函数已经定义是一个既可以压缩空白也可以不压缩空白的函数。现在你看出来为什么使用 strNone 转化为一个字符串很重要了。processFunc 假设接收到一个字符串参数然后调用 split 方法,如果你传入 None ,将导致程序崩溃,因为 None 没有 split 方法。

再往回走一步,你再一次使用了字符串格式化来连接 processFunc 的返回值 和 methodljust 方法的返回值。ljust 是一个你之前没有见过的新字符串方法。

例 4.24. ljust 方法介绍

>>> s = 'buildConnectionString'
>>> s.ljust(30) 
'buildConnectionString         '
>>> s.ljust(20) 
'buildConnectionString' 
[1]ljust 用空格填充字符串以符合指定的长度。info 函数使用它生成了两列输出并将所有在第二列的 doc string 纵向对齐。
[2]如果指定的长度小于字符串的长度,ljust 将简单地返回未变化的字符串。它决不会截断字符串。

几乎已经完成了。有了 ljust 方法填充过的方法名称和来自调用 processFunc 方法得到的 doc string (可能压缩过),你就可以将两者连接起来并得到单个字符串。因为对 methodList 进行了映射,最终你将获得一个字符串列表。利用 "\n"join 函数,将这个列表连接为单个字符串,列表中每个元素独占一行,接着打印出结果。

例 4.25. 打印列表

>>> li = ['a', 'b', 'c']
>>> print "\n".join(li) 
a
b
c 
[1]在你处理列表时,这确实是一个有用的调试技巧。在 Python 中,你会十分频繁地操作列表。

上述就是最后一个令人困惑的地方了。但是现在你应该已经理解这段代码了。

 print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList]) 

4.9. 小结

4.9. 小结

apihelper.py 程序和它的输出现在应该非常清晰了。

 def info(object, spacing=10, collapse=1):
    """Print methods and doc strings.
    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])
if __name__ == "__main__":
    print info.__doc__ 

apihelper.py 的输出:

>>> from apihelper import info
>>> li = []
>>> info(li)
append     L.append(object) -- append object to end
count      L.count(value) -> integer -- return number of occurrences of value
extend     L.extend(list) -- extend list by appending list elements
index      L.index(value) -> integer -- return index of first occurrence of value
insert     L.insert(index, object) -- insert object before index
pop        L.pop([index]) -> item -- remove and return item at index (default last)
remove     L.remove(value) -- remove first occurrence of value
reverse    L.reverse() -- reverse *IN PLACE*
sort       L.sort([cmpfunc]) -- sort *IN PLACE*; if given, cmpfunc(x, y) -> -1, 0, 1 

在研究下一章前,确保你可以无困难的完成下面这些事情:

  • 用可选和命名参数定义和调用函数
  • str 强制转换任意值为字符串形式
  • getattr 动态得到函数和其它属性的引用
  • 扩展列表解析语法实现列表过滤
  • 识别 and-or 技巧并安全地使用它
  • 定义 lambda 函数
  • 将函数赋值给变量然后通过引用变量调用函数。我强调的已经够多了:这种思考方式对于提高对 Python 的理解力至关重要。在本书中你会随处可见这种技术的更复杂的应用。