Python

Table of Contents

1. Python 简介

Python was conceived in the late 1980s, and its implementation was started in December 1989 by Guido van Rossum at CWI in the Netherlands as a successor to the ABC language capable of exception handling and interfacing with the Amoeba operating system. Python supports multiple programming paradigms, including object-oriented, imperative and functional programming or procedural styles.

Python 历史版本如表 1 所示。

Table 1: Python 历史版本
Version Release date
Python 1.0 January 1994
Python 1.5 December 31, 1997
Python 1.6 September 5, 2000
Python 2.0 October 16, 2000
Python 2.1 April 17, 2001
Python 2.2 December 21, 2001
Python 2.3 July 29, 2003
Python 2.4 November 30, 2004
Python 2.5 September 19, 2006
Python 2.6 October 1, 2008
Python 2.7 July 3, 2010
Python 3.0 December 3, 2008
Python 3.1 June 27, 2009
Python 3.2 February 20, 2011
Python 3.3 September 29, 2012
Python 3.4 2014-03-17
Python 3.5 2015-09-13
Python 3.6 2016-12-23

参考:
本文主要摘自《Python 参考手册(第 4 版,Python Essential Reference)》
Python Essential Reference, 4th Edition (online pdf version)
https://docs.python.org/2/tutorial/index.html
https://docs.python.org/3/tutorial/index.html

1.1. Hello world 程序

Python 语句可直接在解释器的交互模式中执行,如:

$ python3
Python 3.4.2 (default, Oct  8 2014, 10:45:20)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello world")
hello world
>>> exit()

python 命令可执行保存在文件中的 Python 脚本,如:

$ cat hello.py
#!/usr/bin/env python
print("hello world")
$ python hello.py
hello world

1.2. 查看帮助

1.2.1. shell 中使用 pydoc 命令

使用 pydoc 可以查看 Python 的帮助文档。如 pydoc if ,又如 pydoc def

1.2.2. 交互方式下使用 help 函数

在交互方式下使用 help 函数,可以查看模块,模块中函数等的帮助文档。如:

>>> import sys
>>> help(sys.exit)
Help on built-in function exit in module sys:

exit(...)
    exit([status])

    Exit the interpreter by raising SystemExit(status).
    If the status is omitted or None, it defaults to zero (i.e., success).
    If the status is an integer, it will be used as the system exit status.
    If it is another kind of object, it will be printed and the system
    exit status will be one (i.e., failure).

2. 词汇和语法约定

2.1. 程序基本结构

Python 程序中的每个语句都以换行符结束。特别长的语句可以使用续行符(\)来分成几行。要在一行上放置多条语句,可以使用分号(;)隔开各条语句。

2.1.1. 注释

和 Shell 类似,Python 中用 # 表示注释。

2.1.1.1. 中文注释

如果要在 python2 的脚本文件里面写中文注释,则必须要在第一行或者第二行声明文件编码的注释,否则 python2 会报错。

声明文件编码的形式为:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

参考:
PEP 263 -- Defining Python Source Code Encodings: https://www.python.org/dev/peps/pep-0263/

2.1.2. 缩进要一致

缩进的空格(或制表符)数目可以是任意的,但是在整个块中的缩进必须一致:

if a:
  statement1
  statement2
else:
  statement3
    statement4        # 缩进不一致,错误!

尽管允许用制表符指示缩进,但这不是一个好习惯(推荐只使用空格作为缩进字符)。

2.2. 变量

Python 是动态语言(变量名没有类型,值有类型)。同一变量名可以(在程序运行的不同阶段)代表不同类型的值。
Python 中变量在赋值中同时生成,无需声明。

$ python3 -q
>>> a = 1;
>>> print(a)
1
>>> a = "test"
>>> print(a)
test

2.2.1. 交互模式中最后一次运算结果

以交互方式使用 Python 时,特殊变量 _ 保存着最后一次运算的结果。

>>> 1 + 3
4
>>> _ + 4
8
>>> print(_)
8

2.3. 数字字面量

内置的数字字面量分为 4 种类型:
(1) 布尔值,标识符 TrueFalse 被解释为布尔值,其整数值分别是 1 和 0
(2) 整数,其位数是任意的,如 1234 ,又如 12345678901234567890 。要使用八进制、十六进制或二进制指定整数,可以在值的前面加上 0, 0x, 或 0b 。例如 0644, 0x100fea8, 0b11101010
(3) 浮点数,如 123.34 ,又如 1.234e+02
(4) 复数,如 1.2 + 12.34j

2.4. 字符串字面量

字符串用单引号('),双引号(")。反斜杠(\)用来转义特殊字符,比如换行符、反斜杠本身、引号以及其他非打印字符。

a = 'Hello world'
b = "Hello world"
c = 'Hello "world"'     # 单引号中可以直接包含双引号
d = "Hello 'world'"     # 双引号中可以直接包含单引号
e = 'Hello \'world\''   # 如果要在单引号中包含单引号,则需要转义
f = "Hello \"world\""   # 如果要在双引号中包含双引号,则需要转义

g = 'Hello \nworld'     # 这里 \n 是换行符,如果想原封不动输出 \n,则可以使用后面的 raw strings

也可以用三引号( '''""" )来定义 ASCII 字符串。 三引号字符串的好处是:字符串中可以包含不必转义的“换行符”和“引号”。 使用它可以方便地创建多行字符串,如:

s="""line 1
line 2
line 3"""

print(s)

相邻两个字符串字面量(两者之间可以包含空格或者换行)会自动进行 concatenation,当然也可以使用 + 进行 concatenation,如:

a = "hello""world"             # 相当于 "helloworld"
b = "hello"    "world"         # 可以用空格(或者换行)分隔,相当于 "helloworld"
c = 'hello' "world"            # 相当于 "helloworld"
d = "hello" + "world"          # 使用 + 也可以 concatenate 两个字符串

使用 len() 可以使用计算字符串中“字符个数”;如果想计算所占“字节数”,则使用 encode('utf-8') 转换为 bytes 后再使用 len() 计算,如:

a = "hello"
print(len(a))                   # 输出 5

b = "汉字"
print(len(b))                   # 输出 2
print(len(b.encode('utf-8')))   # 输出 6

2.4.1. raw strings

字符串前加 r 或者 R 前缀表示 raw strings。在 raw strings 中, \ 没有特殊含义,如 r"\n" 表示包含两个字符(即字符 \ 和字母 n )的字符串。raw strings 可以简化正则表达式的书写。

2.5. bytes 字面量

bytes 字面量使用前缀 b 或者 B 表示,如:

a = b'abcd'           # 使用双引号也行,即 b"abcd" 也行
print(a.hex())        # 输出 61626364
b = b'ABCD'
print(b.hex())        # 输出 41424344
c = b'\0'
print(c.hex())        # 输出 00

在 bytes 字面量只能包含 ascii 字符,所以大于等于 128 的表示需要使用 \x 转义,如:

a = b'\xde\xad\xbe\xef'
print(a.hex())        # 输出 deadbeef

2.5.1. bytes 和字符串的相互转换

在 bytes 上调用方法 decode 可以转移为字符串,而在字符串上调用方法 encode 可以转移为 bytes,如:

a = b'XYZ'                           # hex 表达为 58595a
# bytes 转换为 hex string
b = a.hex()                          # str 58595a
# 从 hex string 构造 bytes
c = bytes.fromhex("58595a")          # b'XYZ'
print("a =", a)                      # a = b'XYZ'
print("b =", b)                      # b = 58595a
print("c =", c)                      # c = b'XYZ'

# bytes 解码为 str
d = b'XYZ'.decode("ascii")           # str XYZ
# str 编码为 bytes
e = 'XYZ'.encode("ascii")            # e 为 b'XYZ', hex 表达为 58595a
print("d =", d)                      # d = XYZ
print("e =", e)                      # e = b'XYZ'

2.6. 容器

2.6.1. 列表([])

列表用 [] 表示,一个列表中的元素不要求同类型,可以是任意的 Python 对象。

列表实例:

>>> names = [ "Dave", "Mark", "Ann", "Phil" ]
>>> names[0] = "Jeff"                             # 访问元素(首元素下标为0)
>>> names.append("Kate")                          # append 增加元素
>>> print(names)
['Jeff', 'Mark', 'Ann', 'Phil', 'Kate']
>>> names.insert(2, "Sydney")                     # insert 在某个位置插入一个元素
>>> print(names)
['Jeff', 'Mark', 'Sydney', 'Ann', 'Phil', 'Kate']
>>> print(names[1:3])                             # 切片操作符(:)访问子列表
['Mark', 'Sydney']
>>> "-".join(names)                               # 字符串函数 join 可以把列表元素按指定的分隔符连接
'Dave-Mark-Ann-Phil'
>>> ":".join(names)
'Dave:Mark:Ann:Phil'
>>> a = [1,2,3] + [4,5]                           # 可用 + 合并两个列表
>>> print(a)
[1, 2, 3, 4, 5]
>>> b = [1,2,3]
>>> b.extend([4,5,6,7])                           # extend 可以用另一列表扩展一个列表
>>> print(b)
[1, 2, 3, 4, 5, 6, 7]
>>> c = [10,20,30,40,50]
>>> c[:3]                                         # 取前 3 个元素,如果 list 不够 3 个也不会报错,返回所有
[10, 20, 30]
>>> c[-4:]                                        # 取后 4 个元素,如果 list 不够 4 个也不会报错,返回所有
[20, 30, 40, 50]

使用 list(range(x,y)) 可以生成从 xy-1 的列表,如:

>>> a = list(range(2, 11))            # 生成 2 到 10 组成的列表
>>> print(a)                          # [2, 3, 4, 5, 6, 7, 8, 9, 10]

使用 for 循环可以遍历列表,使用内置函数 enumerate 可以遍历列表的同时获得索引。如:

items = [1, 2, 3, 4, 5]
for item in items:
    print(item)


items = [1, 2, 3, 4, 5]
for index, item in enumerate(items):  # 返回两个值,前一个为索引,后一个为列表中的元素
    print(index)
    print(item)
2.6.1.1. 遍历列表时删除元素

遍历列表,当元素满足指定条件时删除该元素。这如何实现呢?

一般,我们采用“从后往前”的遍历方式:

# 从后往前遍历列表
for i in range(len(list1) - 1, -1, -1):   # 从后往前遍历(方式1)
#for i in reversed(range(0, len(list1))): # 从后往前遍历(方式2),用内置函数reversed实现反转
#for i in range(0, len(list1))[::-1]:     # 从后往前遍历(方式3),用slice操作实现反转
    if check(list1[i]):
        del list1[i]

也可以采用“从前往后”的遍历方式,只是代码看起来有点啰嗦:

# 从前往后遍历列表
i = 0
n = len(list1)
while i < n:
    if check(list1[i]):
        del list1[i]
        n = n - 1
    else:
        i = i + 1

参考:https://stackoverflow.com/questions/6022764/python-removing-list-element-while-iterating-over-list

2.6.2. 元组(())

元组(tuple)类型和列表关系很密切,通过用 () 中将一系列逗号分割的值括起来可以得到一个元组。元组支持大多数列表的操作,比如索引,切片和连结。
元组是只读的,创建元组后,不可修改其中的元素,也不能添加或删除元素。

>>> a = (1, 4, 5, -9, 10)
>>> type(a)
<type 'tuple'>
>>> print(a[0])
1
>>> print(a[0:2])
(1, 4)

即使没有圆括号,Python 通常也能识别出元组。如:

>>> address = 'www.python.org', 80
>>> type(address)
<type 'tuple'>
>>> print(address[0])
www.python.org
>>> print(address[1])
80

2.6.3. 集合(set)

集合用于包含一组无序的对象,集合中的元素不能重复。要创建集合,可使用 set() 函数。如:

>>> s = set([3, 5, 9, 10])   # 创建一个数值集合
>>> t = set("Hello")         # 创建一个字符的集合,这个集合中有4个元素:H, e, l, 0
>>> list(s)                  # 通过 list 可以把 set 转换为列表
[9, 10, 3, 5]
>>> list(t)                  # 通过 list 可以把 set 转换为列表
['e', 'H', 'l', 'o']
>>> sorted(s)                # 如果转换为一个排序的列表,则可以使用 sorted
[3, 5, 9, 10]
>>> sorted(t)                # 如果转换为一个排序的列表,则可以使用 sorted
['H', 'e', 'l', 'o']

集合支持一系列标准操作,包括并集、交集、差集和对称差集。如:

a = t | s     # t和s的并集
b = t & s     # t和s的交集
c = t - s     # 求差集(项在t中,但不是s中)
d = t ^ s     # 对称差集(项在t或s中,但不会同时出现在二者中)

使用 add()update() 可以在集合中添加新项:

t.add('x')                 # 添加一项
s.update([10, 37, 42])     # 在s中添加多项

使用 remove() 可以删除一项:

t.remove('H')

2.6.4. 字典({key:value})

字典就是一个关联数组(或称为哈希表)。它是一个通过关键字索引的对象的集合。使用 {} 来创建一个字典。

a = {
   "username" : "beazley",
   "home" : "/home/beazley",
   "uid" : 500 }
print(a["username"])               # 输出 'beazley'

if "username" in a:                # 用 in 关键字测试字典中 key 是否存在。
  username = a["username"]
else:
  username = "unknown user"

del a["username"]                  # 用 del 删除字典中元素

a["alias"] = "bz"                  # 往 dict 中增加新对象

使用 keys 方法可以得到所有的键,使用 items 方法可以遍历字典。如:

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> knights.keys()
dict_keys(['gallahad', 'robin'])
>>> for k, v in knights.items():
...     print(k, v)
...
gallahad the pure
robin the brave

3. 类型与对象

Python 程序中保存的所有数据都是围绕“对象”这个概念来构建的。对象包括一些基本的数据类型,如数字、字符串、列表和字典。也可以通过类的形式创建用户定义的对象。

3.1. 对象基本概念

程序中存储的所有数据都是对象。 每个对象都有一个“身份(identity)”,一个“类型(type)或者称为类别(class)”和一个“值(value)”。 例如,执行 a = 42 这行代码时,就会使用值 42 创建一个整数类型对象, 对象身份(identity)可以看作是指向它在内存中所处位置的指针, 这个例子中 a 就是引用这个具体位置的名称。

3.1.1. 对象身份(id())和对象类型(type())

内置函数 id() 可返回一个对象的身份(identity),返回值为整数。这个整数通常对应于该对象在内存中的位置,不过这是和具体的实现相关。 is 运算符用于比较两个对象的身份。

内置函数 type() 可返回一个对象的类型。由于对象的类型本身也是一个对象(该对象的定义是唯一的,对于某个类型的所有实例都是相同的),因此,类型之间可以使用 is 运算符进行比较。

下面的例子说明了比较两个对象的不同方式:

# Compare two objects
def compare(a,b):
  if a is b:
    # a and b are the same object
    statements
  if a == b:
    # a and b have the same value
    statements
  if type(a) is type(b):
    # a and b have the same type
    statements

3.2. 可变对象(mutable)和不可变对象(immutable)

前面说过,每个对象都有一个“身份(identity)”,一个“类型(type)或者称为类别(class)”和一个“值(value)”。

对象被创建之后,它的身份(identity)和类型(或称为类别)就不可以改变。如果对象的值是可以修改的,则称为可变对象(mutable)。如果对象的值不可以修改,则称为不可变对象(immutable)。 Python 中常见 immutable/mutable 对象如表 2 所示。

Table 2: Python 中常见 immutable/mutable 对象
immutable types mutable types
int, float, long, complex byte array
str list
bytes set
tuple dict
frozen set  

下面是不可变对象(int)的简单测试:

>>> x = 1
>>> type(x)
<type 'int'>
>>> id(x)
140377823681400
>>> x += 3
>>> id(x)
140377823681328    # 和上一个id(x)的输出不同。由于int是“不可变”的,当执行x += 3时,无法修改x关联的对象,变量x会关联到另一个对象上

下面是可变对象(list)的简单测试:

>>> x = [1, 2, 3]
>>> type(x)
<type 'list'>
>>> id(x)
4317624728
>>> x += [4, 5]
>>> id(x)
4317624728        # 和上一个id(x)的输出相同。由于list是“可变”的,当执行x += [4, 5]时,可直接修改x关联的对象

关于可变对象和不可变对象的另一个测试:

def append_to_sequence (myseq):
    myseq += (9,9,9)
    return myseq

tuple1 = (1,2,3)         # tuples are immutable
list1  = [1,2,3]         # lists are mutable

tuple2 = append_to_sequence(tuple1)
list2  = append_to_sequence(list1)

print(tuple1)   # outputs (1, 2, 3)
print(tuple2)   # outputs (1, 2, 3, 9, 9, 9)
print(list1)    # outputs [1, 2, 3, 9, 9, 9]
print(list2)    # outputs [1, 2, 3, 9, 9, 9]

3.2.1. a=a+b (assignment-statements)和 a+=b (augmented assignment-statements)的区别

Python 中 = 是 assignment-statements,可理解为名字绑定,将其右边表达式的结果(值或对象)绑定到其左边的名字上。
+=, -=, <<= 等称为 augmented assignment-statements,它对左边对象尽可能地进行 in-place 操作。如果左边对象是 mutable 的,则直接原地修改对象的值;如果左边对象是 immutable 的,则无法进行 in-place 操作,这时和 assignment-statements 类似。

>>> x = [1, 2]
>>> type(x)
<type 'list'>
>>> id(x)
4317624584
>>> x = x + [3, 4]
>>> id(x)
4317624728             # 和上一个id(x)的输出不同,说明执行x = x + [3, 4]后(名字重新绑定),变量x关联到了不同的对象
>>> x += [5, 6]
>>> id(x)
4317624728             # 和上一个id(x)的输出相同,说明执行x += [5, 6]后(原地修改),变量x关联的对象在内存中的位置没有变化

参考:https://docs.python.org/3/reference/simple_stmts.html#augmented-assignment-statements

3.3. 属性和方法

大多数对象都拥有大量特有的数据属性和方法。“属性”就是与对象相关的值。方法就是被调用时将在对象上执行某些操作的函数。使用点运算符(.)可以访问属性和方法。如:

a = 3 + 4j       # 创建一个复数(对象)
r = a.real       # 获得实部(复数对象的属性之一)

b = [1, 2, 3]    # 创建一个列表(对象)
b.append(7)      # 使用列表对象的append方法添加一个新元素

3.3.1. 列出对象的属性和方法(dir())

使用 dir() 函数可以列出对象的属性和方法。 。 如:

>>> items = [23, 45]
>>> dir(items)            # dir() 函数可以列出对象上的所有可用方法
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

3.4. First-Class Objects

Python 中的所有对象都是“第一类的”。能够命名的所有对象都可以当作数据处理。例如,下面给出了一个包含两个值的字典:

items = {
  'number' : 42,
  'text' : "Hello World"
}

给这个字典添加一些不平常的项,便可以理会到“所有对象是第一类的”这句话的含义,例如:

items["func"] = abs             # Add the abs() function
import math
items["mod"] = math             # Add a module
items["error"] = ValueError     # Add an exception type
nums = [1,2,3,4]
items["append"] = nums.append   # Add a method of another object

在这个例子中,items 字典包含一个函数、一个模块、一个异常和另一个对象的一个方法。我们可以使用 items 的字典查询代替原始名称,代码依然有效,例如:

>>> items["func"](-45)             # 执行abs(-45)
45
>>> items["mod"].sqrt(4)           # 执行math.sqrt(4)
2.0
>>> try:
...   x = int("a lot")
... except items["error"] as e:    # 等同于except ValueError as e:
...   print("Couldn't convert")
...
Couldn't convert
>>> items["append"](100)           # 执行nums.append(100)
>>> nums
[1, 2, 3, 4, 100]

3.5. 表示数据的内置类型

Python 中表示数据的内置类型如表 3 所示。

Table 3: Built-In Types for Data Representation
Type Category Type name Desciption
None type (None) The null object None
Numbers int Integer
  long Arbitrary-precision integer (Python 2 only)
  float Floating point
  complex Complex number
  bool Boolean (True or False)
Sequences str Character string
  unicode Unicode character string (Python 2 only)
  list List
  tuple Tuple
  xrange A range of integers created by xrange() (In Python 3, it is called range.)
Mapping dict Dictionary
Sets set Mutable set
  frozenset Immutable set

4. 运算符和表达式

4.1. 算术运算符

算术运算符如表 4 所示。

Table 4: Numeric Operations
Operation Result
x + y sum of x and y
x - y difference of x and y
x * y product of x and y
x / y quotient of x and y
x // y floored quotient of x and y
x % y remainder of x / y
-x x negated
+x x unchanged
abs(x) absolute value or magnitude of x
int(x) x converted to integer
float(x) x converted to floating point
complex(re, im) a complex number with real part re, imaginary part im. im defaults to zero.
c.conjugate() conjugate of the complex number c
divmod(x, y) the pair (x // y, x % y)
pow(x, y) x to the power y
x ** y x to the power y

下面例子演示了 / 运算符和 // 运算符的不同:

>>> 5/2                       # 返回浮点数
2.5
>>> 5//2                      # 相除后,取整数部分
2
>>> type(5/2)
<class 'float'>
>>> type(5//2)
<class 'int'>

当我们想有得到相除后的整数部分时,应该使用 x//y ,而不是 int(x/y) ,它们有区别:

>>> int(999999999999999999999999/3)    # 使用 / 运行得到浮点数,再转 int 会有精度损失
333333333333333327740928
>>> 999999999999999999999999//3        # 使用 // 直接得到 int,不会有精度损失
333333333333333333333333

4.2. 布尔表达式和真值

and, ornot 关键字构成了布尔表达式。它们的行为如表 5 所示。

Table 5: 布尔表达式
Operator Description
x or y 如果 x 为 False,则返回 y;否则返回 x
x and y 如果 x 为 False,则返回 x;否则返回 y
not x 如果 x 为 False,则返回 True;否则返回 False

注 1: 使用表达式来判断 True 或者 False 值时,“True,任意非零数字、非空字符串、非空列表、非空元组或非空字典”都将返回 True,而“False,零、空字符串、空列表、空元组或空字典”都将返回 False。
注 2: andor 都具有“短路求值”特性。

4.3. 比较运算符

比较运算符返回布尔值。如表 6 所示。

Table 6: Comparison Operations
Operation Meaning
< strictly less than
<= less than or equal
> strictly greater than
>= greater than or equal
== equal
!= not equal
is object identity
is not negated object identity

4.4. 条件表达式

常见的编程模式是根据表达式的结果,有条件地进行赋值,例如:

if a <= b:
    minvalue = a
else:
    minvalue = b

使用“条件表达式”可以简化上面代码,例如:

minvalue = a if a <= b else b         # 条件表达式实例

在“条件表达式”中,首先求值的是中间的条件。如果结果为 True,再对 if 语句左面的表达式求值,否则就会对 else 后面的表达式求值。

使用条件表达式时应该格外仔细,因为它们可能导致混淆,特别是将其嵌套或者与其他复杂表达式混在一起的时候。然而,条件表达式在列表推导中特别有用,例如:

values = [1, 100, 45, 23, 73, 37, 69]
clamped = [x if x < 50 else 50 for x in values]
print(clamped)     # 输出 [1, 50, 45, 23, 50, 37, 50]

4.5. 对象的相等性(==)和相同性(is)测试

使用 == 可以判断两个对象的值是否相同,使用 is 可以判断两个对象是否是同一个对象(内存地址是否相同)。

对于自定义的类,通过实现 __eq__ 方法可定制 == 的形为。如:

class Test(object):

    def __init__(self, attr1, attr2):
        self.attr1 = attr1
        self.attr2 = attr2

    def __str__(self):
        return str(self.__dict__)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

t1 = Test("foo", 3)
t2 = Test("foo", 3)
t3 = Test("bar", 3)

print(t1)
print(t2)
print(t3)
print(t1 == t2)  # True
print(t2 == t3)  # False

参考:https://docs.python.org/3.5/reference/datamodel.html#basic-customization

4.6. 字符串格式化

4.6.1. %-format(不推荐)

取模运算符 s % d 可以生成格式化的字符串,其中 s 是一个格式字符串,而 d 是一个对象组或映射对象(字典),形为类似于 C 语言的 sprintf 函数。如:

a = 42
b = 13.142783
s1 = "a is %d" % a       # s1 为 "a is 42"
s2 = "%10d %f" % (a, b)  # s2 为 "        42 13.142783"

注:这是古老的字符串格式化方法,不再推荐使用。

4.6.2. format 函数

使用字符串的 s.format(*args, *kwargs) 方法可以格式化字符串。如:

name = 'world'
prog = 'Python'
str1 = 'Hello {0}, {1}!'.format(name, prog)
print(str1)         # 输出 Hello world, Python!

除了在 {} 中使用数字外,还可以直接使用名字。如:

str1 ='Hello {name}, {prog}!'.format(name='world', prog='Python')
print(str1)         # 输出 Hello world, Python!

4.6.3. Template Strings

下面是 Template Strings 的使用实例:

import string
str1 = string.Template('Hello $name, This is $prog!')
print(str1.substitute(name='world', prog='Python'))  # 输出 Hello world, This is Python!

4.7. 切片操作

切片操作可以应用于有序对象(如 string,tuple 或 list),用于从有序对象中选出指定范围的元素。切片的基本语法为:

[start : stop : stride]   # 分别表示“开始下标”(包含),“结束下标”(不包含)和“步进”

说明 1:“步进”和紧根在前面的冒号可以省略,默认步进为 1。
说明 2:“开始下标”省略时,默认为 0;“结束下标”省略时,默认为有序对象的长度。
说明 3:Python 中切片操作得到的是有序对象的复本;而 NumPy 中的切片操作得到的是原始数组的一个视图,也就是说它与原始数组共享同一块数据空间。

下面是一些切片操作的基本实例:

>>> word='Python3'
>>> word[1:3]    # 从下标1(包含)开始,到下标3(不包含)结束
'yt'
>>> word[:3]     # 从下标0(包含)开始,到下标3(不包含)结束
'Pyt'
>>> word[1:]     # 从下标1(包含)开始,到下标7(不包含)结束
'ython3'
>>> word[1:5:2]  # 从下标1(包含)开始,到下标5(不包含)结束,步进为2
'yh'
>>> word[::1]    # 相当于复制数组
'Python3'
>>> word[::-1]   # 相当于复制倒序后的数组
'3nohtyP'

参考:https://docs.python.org/3/reference/expressions.html?#slicings

5. 程序结构与控制流

5.1. 条件语句

条件语句的基本格式如下所示:

if a < b:
  z=b
else:
  z=a

如果某个子句不需要任何操作,可以使用 pass 语句。如:

if a < b:
  pass
else:
  z=a

通过使用 or, andnot 关键字你可以建立任意的条件表达式。如:

if b >= a and b <= c:
  print("b is between a and c")
if not (b < a or b > c):
  print("b is still between a and c")

elif 语句可以检验多重条件。如:

if a == '+':
  op = PLUS
elif a == '-':
  op = MINUS
elif a == '*':
  op = MULTIPLY
else:
  raise RuntimeError, "Unknown operator"

5.2. 循环语句

可以使用 whilefor 语句实现循环。 break 语句用于立刻中止循环。 continue 语句用于直接进入下一次循环(忽略当前循环的剩余语句)。

while expression:
     statements

while 语句循环执行块中的语句,直到表达式为假。

for i in s:
     statements

for 语句反复迭代一个序列中的元素,直到迭代完序列中最后一个元素。

下面是 for 语句的一些例子:

for i in range(0, 5):
    print(i)                           # 依次输出 5 个数,从 0 到 4

# 遍历 list 的例子
items = ["a", "b", "c"]
for item in items:
    print(item)

for index, item in enumerate(items):   # 返回两个值,前一个为索引,后一个为列表中的元素
    print(index)                       # 输出每个索引
    print(item)                        # 输出每个元素

# 遍历 dict 的例子
my_dict = {'color': 'blue', 'fruit': 'apple', 'pet': 'dog'}
for key in my_dict:                    # 直接遍历 dict,会返回 key
    print(key, '->', my_dict[key])

for key, value in my_dict.items():     # 返回两个值,前一个为 key,后一个为 value
    print(key, '->', value)

注:如果你想退出一个多重循环,可以使用异常。Python 不提供 goto 语句。

5.3. 异常

使用 tryexcept 语句来捕获并处理异常。

try:
  f = open("file.txt","r")
except OSError as err:
  print(err)

如果想捕获所有异常,则可以这样:

try:
  whatever()
except:                         # 捕获所有异常
  print("Caught it!")

如果想捕获所有异常的同时还打印出异常,则可以这样:

try:
  whatever()
except BaseException as e:      # 捕获所有异常
  print(e)

所有异常中包含了 KeyboardInterrupt 异常,如果我们不小心捕获并吞下了这个异常,则可能导致程序不能人为退出了。更常见的是捕获 Exception,如:
不过,一般情况下,我们

try:
  whatever()
except Exception as e:          # 捕获绝大部分异常(不包含 SystemExit,KeyboardInterrupt 异常)
  print(e)

Python 中异常层次如下所示:

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError

参考:https://docs.python.org/3.9/library/exceptions.html#exception-hierarchy

5.3.1. raise

raise 语句用来有意引发异常,你可以使用内建异常来引发异常。如:

raise Exception('spam')

5.4. 上下文管理器与 with 语句

with 语句是从 Python 2.5 开始引入的一种与异常处理相关的语句(Python 2.5 中要通过 from __future__ import with_statement 导入后才可以使用),从 Python 2.6 开始缺省可用。

正确地管理各种系统资源(如文件、锁、连接),在涉及异常时通常是一个棘手的问题:引发的异常可能导致控制流跳过负责释放关键资源的语句。

先看一个使用 with 的例子:

with open("debuglog","a") as f:
    f.write("Debugging\n")
    ## statements
    f.write("Done\n")

这将保证离开 with 后,文件会被关闭。

我们先介绍上下文管理器的概念。 如果对象实现了 __enter__()__exit__() 方法,这个对象就称为上下文管理器(Context Manager)。

with 语句的功能是:在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。

参考:https://docs.python.org/2/reference/compound_stmts.html#the-with-statement

6. 函数及函数编程

6.1. 函数定义(def)

在 Python 中,使用 def 语句来创建函数。调用函数和 C 语言类似。

函数实例:

def remainder(a,b):
    q = a/b
    r = a - q*b
    return r

Python 中不支持函数重载。

6.2. 函数返回值

当函数中省略 return 语句时,默认返回 None 。如:

>>> def foo():
...   x=1           # 函数foo没有return语句
...
>>> a=foo();
>>> print(a)
None
>>> type(a)
<type 'NoneType'>

使用元组可以让函数返回多个值。如:

def divide(a,b):
  q = a // b
  r = a - q*b
  return (q,r)            # 不写小括号,也行 return q, r

# 把函数的多个返回值放到单独的变量中:
quotient, remainder = divide(1456, 33)       # 1456=44*33+4, quotient=44, remainder=4
(quotient, remainder) = divide(1456, 33)     # 和上一行作用相同

6.3. 参数传递方式(传对象引用)

Python 不允许程序员选择采用传值还是传引用。Python 参数传递采用的是“传对象引用(call by object reference)”的方式。实际上,这种方式相当于传值和传引用的一种综合。 如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值——相当于通过“传引用”来传递对象。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象——相当于通过“传值”来传递对象。

a = [1, 2, 3, 4, 5]
def square(items):
  for i,x in enumerate(items):
    items[i] = x * x

square(a)       # a 会被修改
print(a)        # a 变为了 [1, 4, 9, 16, 25]

6.4. 参数默认值

如果在函数定义时为参数赋值(这就是参数的默认值),则这个参数是可选的,调用函数可以省略它。

def add(x, y=2):
    return x+y

add(1, 10)      # return 11
add(1)          # return 3

定义函数后,默认的参数值会传递给以值的方式提供的对象。如:

a = 10
def foo(x=a):
  return x

a = 5             # 给a重新赋值
print(foo())      # 输出10(默认值没有变)

6.4.1. 函数参数默认值仅执行一次(“可变对象”为默认值时应注意)

函数参数默认值仅执行一次。当默认值为“可变对象”时,这可能会带来副作用。如:

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))    # output [1]
print(f(2))    # output [1, 2]
print(f(3))    # output [1, 2, 3]

如果你希望在不同的函数调用中不要共享函数参数默认值,你可以这样写:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

print(f(1))    # output [1]
print(f(2))    # output [2]
print(f(3))    # output [3]

6.5. 可变长参数(*)

如果给最后一个参数名加上星号(*),函数就可以接受任意数量的参数。如:

def mySum(*args):
  sum = 0
  for i in args:          # args is 'tuple'
    sum = sum + i
  return sum

print(mySum(1, 2, 3, 4))   # output 10

在 Python3 中,可变长参数后,还可以出现带有默认值的参数。如:

>>> def concat(*args, sep="/"):                   # Python 2中不合法,Python 3中合法
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

6.6. 关键字参数

调用函数时,可以显式地命名每个参数并为其指定一个值,这称为关键字参数。使用关键字参数时,参数的顺序无关紧要。位置参数和关键字参数可以同时使用,不过要求位置参数先出现。如:

def foo(w, x, y, z):
  statements

# 关键字参数调用
foo(w='hello', x=3, y=22, z=[1,2])
foo(w='hello', z=[1,2], x=3, y=22)   # 和上行相同。关键字参数的顺序无关紧要
foo('hello', 3, y=22, z=[1,2])       # 和上面两行相同,位置参数和关键字参数可以同时使用(前提是位置参数先出现)
foo('hello', 3, z=[1,2], y=22)       # 和上面三行相同

6.6.1. 用字典保存额外关键字参数(**)

如果函数定义的最后一个参数以两个星号(**)开头,可以把所有额外的关键字参数都放入一个字典中并把这个字典传递给函数。如果要编写的函数接受大量可扩展的配置选项作为参数,则使用 ** 开头的参数就非常方便。如:

def make_table(data, **parms):
  fgcolor = parms.pop("fgcolor")      # parms is 'dict'
  bgcolor = parms.pop("bgcolor")
  border = parms.pop("border", 1)
  width = parms.pop("width", 100)

  if parms:
    raise TypeError("Unsupported configuration options %s" % list(parms))

make_table(items, fgcolor="black", bgcolor="white")
make_table(items, fgcolor="black", bgcolor="white", border=1, width=400)

** 和可变长参数列表(*)可以一起使用,不过要求 ** 参数出现在最后。如:

# Accept variable number of positional or keyword arguments
def spam(*args, **kwargs):
  # args is a tuple of positional args
  # kwargs is dictionary of keyword args
  ...

这其他了函数编写包装器和代理时经常使用 *args**kwargs (名字 args 和 kwargs 换为其它名字也行)。例如下面的 callfunc()函数接受参数的任意组合,并把它们传递给 func()函数。

def callfunc(*args, **kwargs):
  func(*args, **kwargs)

6.7. Unpacking Argument Lists

使用 *** 可以进行 Unpacking Argument Lists,它使得为函数提供函数更加方便。下面通过两个例子来介绍什么是 Unpacking Argument Lists。

def fun(a, b, c, d):
    print(a)
    print(b)
    print(c)
    print(d)

my_list=['value1', 'value2', 'value3', 'value4']

#fun(my_list)                  # 会报错 TypeError: fun() takes exactly 4 arguments (1 given)
fun('value1', 'value2', 'value3', 'value4')
fun(*my_list)                  # Unpacking Argument Lists,和上一行作用相同

再看一个 Unpacking Argument Lists 的例子。

def fun(a, b, c, d):
    print(a)
    print(b)
    print(c)
    print(d)


my_dict={'a':'value1', 'b':'value2', 'c':'value3', 'd':'value4'}

#fun(my_dict)                  # 会报错 TypeError: fun() takes exactly 4 arguments (1 given)
fun(a='value1', b='value2', c='value3', d='value4')
fun(**my_dict)                 # Unpacking Argument Lists,和上一行作用相同

参考:https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists

6.8. Function Annotations(为函数参数和返回值标记类型)

可以为用户自定义函数的参数和返回值增加类型说明,如:

>>> def f(ham: str, eggs: str = 'eggs') -> str:
...     print("Annotations:", f.__annotations__)
...     print("Arguments:", ham, eggs)
...     return ham + ' and ' + eggs
...
>>> f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

详情可参考 PEP 3107PEP 484

6.8.1. 使用 mypy 进行静态类型检测

为函数参数和返回值增加的类型说明后,使用工具 mypy 可以对程序进行静态类型检测。mypy 的安装和基本使用如下:

$ pip3 install mypy
$ mypy program1.py

下面是一个有问题的代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def f(ham: str, eggs: str = 'eggs') -> str:
     print("Annotations:", f.__annotations__)
     print("Arguments:", ham, eggs)
     return ham + ' and ' + eggs

f(100)                        # 这是错误的!我希望在运行前就发现它

上面代码中 f(100) 是错误的,参数类型为 str,但传参却为 int,使用工具 mypy 可以在运行之前就发现该问题:

$ mypy program1.py
program1.py:9: error: Argument 1 to "f" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)

6.9. 作用域规则(静态作用域)

每次执行一个函数时,就会创建新的局部命名空间。该命名空间代表一个局部环境,其中包含函数参数的名称和在函数体内赋值的变量名称。 Python 采用 Lexical scoping(“词法作用域”,也可称为“静态作用域”),即解析这些名称时,解释器将首先搜索局部作用域,然后由内而外一层层检查外部嵌套函数定义的作用域。如果找不到匹配,最后将搜索全局命名空间和内置命名空间。

def fun():
    a = 1
    def display():
        print(a)
    return display

a = 2

f = fun()
f()              # 由于Python采用“静态作用域”,会去函数定义环境(而不是调用链)中寻找变量,所以这里会输出1(而不是2)。

注:除词法作用域外,还存在“动态作用域”,采用“动态作用域”的语言在解析名称时,会一层层往往外查找函数的调用链(而不是函数的定义环境),这会使得程序的调试变得更加复杂,且容易出错。

6.9.1. 给全局作用域变量重新赋值(global)

要想在一个函数内部改变一个全局变量的值,需要在函数内部使用 global 声明变量:

a = 1.1
b = 2.2

def foo():
    global a
    a = 11.11       # modify global variable a
    b = 22.22
    return

foo()
print(a)    # output 11.11
print(b)    # output 2.2     (b在函数foo中没有声明为global)

6.9.2. 给外部函数中的变量重新赋值(nonlocal)

Python2 只支持在最里层的作用域(即局部变量)和全局命名空间(使用 global)中给变量重新赋值。Python2 中,内部函数不能给定义在外层函数中的局部变量重新赋值。

def outer():
    x = 1
    def inner():
        x = 2
        print(x)    # 输出2
    inner()
    print(x)        # 输出1,外层的x没有被inner函数修改

outer()

在 Python2 中,如果想要修改外层函数中的局部变量,可以把要修改的值放在列表或字典中。参考:http://stackoverflow.com/questions/8447947/is-it-possible-to-modify-variable-in-python-that-is-in-outer-but-not-global-sc

在 Python3 中,引入了 nonlocal 语句,可以给外部函数中的变量重新赋值。如:

def outer():
    x = 1
    def inner():
        nonlocal x  # Python 3中增加了nonlocal
        x = 2
        print(x)    # 输出2
    inner()
    print(x)        # 输出2,外层的x被inner函数修改了

outer()

6.10. 闭包

如果函数(要满足后面条件只能是嵌套函数)访问的变量中存在变量满足条件:即不是全局变量又不是局部变量,这时函数和变量一起构成“闭包”。

>>> def make_printer(msg):
...   def printer():
...      print(msg)                      # msg不是全局变量,也不是函数printer的局部变量
...   return printer
...
>>> printer1 = make_printer('Foo!')      # printer1是闭包
>>> printer1.__closure__
(<cell at 0x10ad83638: str object at 0x10ad82c60>,)
>>> printer1()
Foo!

下面的 printer2 不是闭包。

>>> def make_printer(msg):
...   def printer(msg=msg):
...      print(msg)                      # msg是printer的参数,它存在于printer的局部作用域中
...   return printer
...
>>> printer2 = make_printer('Foo!')      # printer2不是闭包
>>> printer2.__closure__                 # 没有输出
>>> printer2()
Foo!

参考:http://stackoverflow.com/questions/4020419/why-arent-python-nested-functions-called-closures

6.10.1. 闭包实例

如里需要在一系列函数调用中保持某个状态,使用闭包是一种非常高效的方式。如:

def countdown1(n):
    def next():
        nonlocal n     # Need Python 3
        r = n
        n -= 1
        return r
    return next

# Example use
next = countdown1(10)
while True:
    v = next()
    print(v)
    if not v: break

上面代码将输出:

$ python3 test.py
10
9
8
7
6
5
4
3
2
1
0

不使用闭包,用类和对象也可以实现类似的功能。如:

class Countdown(object):
    def __init__(self,n):
        self.n = n
    def next(self):
        r = self.n
        self.n -= 1
        return r

# Example use
c = Countdown(10)
while True:
    v = c.next()
    print(v)
    if not v: break

有测试表明,上面例子中,增大起始值时(如把 10 换为一个非常大的数), 使用闭包的版本比使用对象的版本其运行速度要快很多。

6.11. Decorators(@)

装饰器(Decorators)是一个函数,其主要用途是包装另一个函数或类。 这种包装的首要目的是透明地修改或增强被包装对象的行为。表示装饰器的语法是特殊符号 @ 。装饰器的语义为把函数替换为经过装饰器处理后的函数,看起来像:

@trace
def square(x):
    return x*x

######## 上面的语义相当于 ########
def square(x):
    return x*x
square = @trace(square)

装饰器本身是函数,下面是它的实例:

enable_tracing = True
if enable_tracing:
    debug_log = open("debug.log","w")

def trace(func):               # 装饰器的实现
    if enable_tracing:
        def callf(*args, **kwargs):
            debug_log.write("Calling %s: %s, %s\n" %
                                (func.__name__, args, kwargs))
            r = func(*args, **kwargs)
            debug_log.write("%s returned %s\n" % (func.__name__, r))
            return r
        return callf
    else:
        return func

@trace
def square(x):
    return x*x

square(3)

运行上面程序,生成的 debug.log 内容如下:

$ cat debug.log
Calling square: (3,), {}
square returned 9

6.12. 生成器与 yield

函数使用 yield 关键字可以定义“生成器”对象。生成器是一个函数,它生成一个值的序列,以便在迭代中使用。

下面是生成器的例子:

def countdown(n):
    print("Counting down from %d" % n)
    while n > 0:
        yield n
        n -= 1
    return

和普通函数不同,调用上面生成器时,其中的代码不会开始执行,它会返回一个生成器对象。只有在生成器对象上第一次调用 next() (在 Python3 中,请使用 __next__() )时才开始执行生成器中代码。

>>> c=countdown(10)      # 调用 countdown(10) 时,并不会开始执行生成器代码
>>> c.next()             # 在Python 3中,请使用c.__next__()
Counting down from 10
10
>>> c.next()
9
>>> c.next()
8

在生成器对象上调用 next() 时,生成器函数将不断执行语句,直到遇到 yield 语句为止。再次调用 next() 时会继续执行直到又遇到 yield 语句。

通常,不会在生成器对象上直接调用 next() 方法,而是在 for 语句, sum() 函数或者一些使用序列的其它操作中使用它。例如:

for n in countdown(10):
    print(n)

又如:

a = sum(countdown(10))
print(a)                     # output 55

6.12.1. 生成器实例

生成器是基于处理管道、流或数据流编写程序的一种极其强大的方式。

下面用生成器实现了类似`tail -f 1.log`的功能:

# python实现 `tail -f 1.log`
import time
def tail(f):
    f.seek(0,2)                 # Move to EOF
    while True:
        line = f.readline()     # Try reading a new line of text
        if not line:            # If nothing, sleep 0.1 second and try again
            time.sleep(0.1)
            continue
        yield line


logs = tail(open("1.log"))
import sys
for line in logs:
    sys.stdout.write(line)

下面是另一个生成器,用于在很多行中查找特定的子字符串:

def grep(lines, searchtext):
    for line in lines:
        if searchtext in line:
            yield line

我们可以把前面的两个生成器(tail 和 grep)合并在一起,创建一个简单的处理管道,实现类似`tail -f 1.log | grep python`功能:

# python实现 `tail -f 1.log | grep python`
logs = tail(open("1.log"))
pylines = grep(logs, "python")
import sys
for line in pylines:
    sys.stdout.write(line)

6.13. 协程与 yield 表达式

在函数内,yield 语句可以作为表达式出现在赋值运算符右边, 例如:

def receiver():
    print "Ready to receive"
    while True:
        n = (yield)
        print("Got %s" % n)

以这种方式使用 yield 语句的函数称为“协程” ,它的执行是为了响应发送给它的值。它的行为也十分类似于生成器,例如:

>>> r = receiver()
>>> r.next()           # 向前执行到第一条yield语句(在Python3中是r.__next__())
Ready to receive
>>> r.send(1)
Got 1
>>> r.send(2)
Got 2
>>> r.send("Hello")
Got Hello

在这个例子中, 对 next()的初始调用是必不可少的,这样协程才能执行可通向第一个 yield 表达式的语句。在这里协程会挂起,等待相关生成器对象 r 的 send() 方法给它发送一个值。传递给 send() 的值由协程中的 (yield) 表达式返回。接收到值后,协程就会执行语句,直至遇到下一条 yield 语句。

下面是 yield 表达式的另一个例子:

def grep(pattern):
    print("Searching for %s" % pattern)
    while True:
        line = (yield)
        if pattern in line:
            print(line)

测试如下:

>>> search = grep('coroutine')
>>> search.next()
Searching for coroutine
>>> search.send('Test')
>>> search.send('I love you')
>>> search.send('I love coroutine')
I love coroutine

参考:https://docs.python.org/2/reference/expressions.html#yield-expressions

6.13.1. 用装饰器保证对 next()的初始调用

在协程中需要首先调用 next() 这件事情很容易被忽略,经常成为错误出现的根源。因此,建议使用一个能自动完成该步骤的装饰器来包装协程。

def coroutine(func):
    def start(*args, **kwargs)
        g = func(*args, **kwargs)
        g.next()
        return g
    return start

使用这个装饰器就可以像下面这样编程和使用协程:

@coroutine
def receiver():
    print("Ready to receive")
    while True:
        n = (yield)
        print("Got %s" % n)

# 实例
r = receiver()
r.send("Hello world")    # 注意:无需初始调用.next()方法

6.13.2. (yield value)

如果 yield 表达式中提供了值,协程可以使用 yield 语句同时接收和发出返回值,例如:

def line_splitter(delimiter=None):
    print("Ready to split")
    result = None
    while True:
        line = (yield result)
        result = line.split(delimiter)

在这个例子中,我们使用协程的方式与前面相同。但是,现在调用 send()方法也会生成一个结果,例如:

>>> s = line_splitter(",")
>>> s.next()
Ready to split
>>> s.send("A,B,C")
['A', 'B', 'C']
>>> s.send("100,200,300")
['100', '200', '300']

send()方法的返回值是传递给下一条 yield 语句的值。

6.14. 列表推导式(List Comprehensions)

函数的常用操作是将函数应用给一个列表的所有项,并使用结果创建一个新列表,例如:

nums = [1, 2, 3, 4, 5]
squares = []
for n in nums:
    squares.append(n * n)

这种操作很常见,因此出现了叫做“列表推导(List Comprehensions)”的运算符,例如:

nums = [1, 2, 3, 4, 5]
squares = [n * n for n in nums]       # 比上面代码块更加简洁

列表推导式用来基于一个列表创建另一个列表 ,它的一般语法为:

[expression for item1 in iterable1 if condition1
            for item2 in iterable2 if condition2
            ...
            for itemN in iterableN if conditionN ]

这种语法大致上等价于以下代码:

result = []
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2:
                ...
                for itemN in iterableN:
                    if conditionN:
                        result.append(expression)

下面是列表推导式的一些例子:

>>> vec = [-2, -1, 0, 1, 2]
>>> [x*2 for x in vec]                  # create a new list with the values doubled
[-4, -2, 0, 2, 4]
>>> [x for x in vec if x >= 0]          # filter the list to exclude negative numbers
[0, 1, 2]
>>> [abs(x) for x in vec]               # apply a function to all the elements
[2, 1, 0, 1, 2]
>>> [(x,y) for x in [-3,5,-10,7] for y in 'abc' if x > 0]
[(5, 'a'), (5, 'b'), (5, 'c'), (7, 'a'), (7, 'b'), (7, 'c')]

注:List Comprehensions 并不是 Python 所独有,其它语言也有类似结构。参考:https://en.wikipedia.org/wiki/List_comprehension

6.14.1. Set and Dicionary Comprehensions

和列表推导式类似,还有“集合推导式”和“字典推导式”。如:

>>> {x*x for x in {1,2}}            # 集合推导式
set([1, 4])
>>> {x: x*2 for x in (1, 2, 3)}     # 字典推导式
{1: 2, 2: 4, 3: 6}

6.15. 生成器表达式

生成器表达式是一个对象,它执行的计算与列表推导式相同,但会迭代地生成结果。 生成器表达式的语法也与列表推导式相同,但要用圆括号代替方括号。 生成器表达式的语法为:

(expression for item1 in iterable1 if condition1
            for item2 in iterable2 if condition2
            ...
            for itemN in iterableN if conditionN)

和列表推导式不同, 生成器表达式不创建列表或者不立即对圆括号内的表达式求值。生成器表达式会创建一个通过迭代并按照需要生成值的生成器对象。 例如:

>>> a = [1, 2, 3, 4]
>>> b = (10*i for i in a)
>>> b
<generator object <genexpr> at 0x103377c30>
>>> b.next()
10
>>> b.next()
20
>>> b.next()
30

生成器表达式和列表推导式的差异在于: 列表推导式会一次创建好包含结果数据的列表;而生成器表达式只是创建生成器。 在某些应用中,生成器表达式会更加高效且内存占用更少。例如:

# Read a file
f = open("data.txt")                           # Open a file
lines = (t.strip() for t in f)                 # Generator expression. Read lines, strip trailing/leading whitespace

comments = (t for t in lines if t[0] == '#')   # Generator expression. All comments
for c in comments:
    print(c)

需要说明的是,在上面的 lines 和 comments 都是生成器表达式。 在上面的处理过程中绝对没有把整个文件加载到内存中。 因此,这里一种从很大的 Python 源文件中提取注释的高效方法。

生成器表达式不会创建序列形式的对象。不能对它进行索引,也不能进行任何常规的列表操作,例如 append()。但是,使用内置的 list() 函数可以将生成器表达式转换为列表。如:

clist = list(comments)                 # comments is generator expression

6.16. 声明式编程(Declarative Programming)

什么是声明式编程(Declarative Programming)呢?声明式编程是和命令式编程(Imperative Programming)相对的概念。C++、Java 等都属命令式编程风格的语言;而 SQL 语言就是声明式编程风格的语言。

声明式编程风格是特点是: 只需要声明你想要得到的结果,而不用显式指定控制流程。 比如下面 SQL 语句:

SELECT * from dogs
INNER JOIN owners
WHERE dogs.owner_id = owners.id

也可以用命令式编程方式实现上面 SQL 语句的逻辑:

// javasript code

//dogs = [{name: 'Fido', owner_id: 1}, {...}, ... ]
//owners = [{id: 1, name: 'Bob'}, {...}, ...]

var dogsWithOwners = []
var dog, owner

for(var di=0; di < dogs.length; di++) {
  dog = dogs[di]
  for(var oi=0; oi < owners.length; oi++) {
    owner = owners[oi]
    if (owner && dog.owner_id == owner.id) {
      dogsWithOwners.push({
        dog: dog,
        owner: owner

      })
    }
  }}
}

声明式编程的好处在于: 代码看起来更加简洁;且它在更高的层次进行了抽象(你只用声明想得到什么,而不用关心怎么得到),这样底层编译器可以帮我们进行优化。

6.16.1. Python 声明式编程实例

列表推导式和生成器表达与声明式编程的操作有很强的联系。

假设文件 portfolio.txt 的内容如下:

AA 100 32.20
IBM 50 91.10
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.10
IBM 100 70.44

我们想要把第二列和第三列相乘后再求和。下面解法是声明式编程:

lines = open("portfolio.txt")
fields = (line.split() for line in lines)
total = sum(float(f[1]) * float(f[2]) for f in fields)
print(total)

对应的命令式编程风格如下:

total = 0
for line in open("portfolio.txt"):
    fields = line.split()
    total += float(fields[1]) * float(fields[2])
print(total)

6.17. lambda 运算符

使用 lambda 语句可以创建表达式形式的匿名函数。语法为:

lambda args: expression

其中 args 是以逗号为分隔的参数列表,而 expression 是用到这些参数的表达式。如:

a = lambda x,y : x+y
r = a(2, 3)             # r = 5

使用 lambda 语句定义的代码必须是合法的表达式,不能出现 for, while 等结构。

7. 类(class)

Python 程序中使用的所有值都是对象。对象由内部数据和各种方法组成。

Python 中,使用 class 语句可定义新的对象类型。

下面是类的定义和实例化的简单例子:

>>> class Complex:
...   def __init__(self, realpart, imagpart):
...     self.r = realpart
...     self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r
3.0
>>> x.i
-4.5

7.1. 继承

Python 中,类继承的语法如下所示:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

如下面是栈的简单实现(它继承自 object,object 是所有 Python 类型的根类型):

class Stack(object):              # 继承object
  def __init__(self):             # __init__ 是个特殊的方法,用来初始化一个对象实例
    self.stack = []
  def push(self,object):
    self.stack.append(object)
  def pop(self):
    return self.stack.pop()
  def length(self):
    return len(self.stack)

由于 Stack 和内置的 list 几乎完全相同,因此,我们还可以通过继承 list 然后添加额外的方法也创建 Stack。如:

class Stack(list):                # 继承list。仅需要添加push方法,list本身带有pop方法
  def push(self, object):
    self.append(object)

可以像下面这样使用上面定义的 Stack 类:

s = Stack()
s.push("Dave")
s.push(43)
x = s.pop()       # x gets 43
print(x)          # output 43
del s

7.2. Abstract Base Classes

使用库 abc 可以实现抽象基类,继承它的类必须实现抽象基类中定义的抽象方法,如:

from abc import ABCMeta, abstractmethod


class Bird(metaclass=ABCMeta):     # metaclass=ABCMeta 表明它是一个抽象基类
    """
    Bird 是抽象基类,不能直接实例化,继承它的类必须实现下面的抽象方法 fly
    """
    @abstractmethod
    def fly(self):
        pass


class Parrot(Bird):    # 方式一:直接继承 Bird
    """
    Parrot 继承于 Bird,必须实现抽象方法 fly
    """
    def fly(self):
        print("Parrot Flying")


@Bird.register      # 方式二:通过装饰器声明 Magpie 是 Bird 的子类
class Magpie:
    """
    Magpie 继承于 Bird,必须实现抽象方法 fly
    """
    def fly(self):
        print("Magpie Flying")


class Crow:
    def fly(self):
        print("Crow Flying")

Bird.register(Crow)     # 方式三:显式地调用 Bird.register 来声明 Crow 是 Bird 的子类


bird = Parrot()
bird.fly()
print(isinstance(bird, Bird))  # True
print(issubclass(Parrot, Bird))  # True

bird = Magpie()
bird.fly()
print(isinstance(bird, Bird))  # True
print(issubclass(Magpie, Bird))  # True

bird = Crow()
bird.fly()
print(isinstance(bird, Bird))  # True
print(issubclass(Crow, Bird))  # True

7.3. 私有成员

Python 中不使用 private 等关键字来表示类中的私有方法或私有实例变量。Python 采用命名约定来表示私有方法或私有变量,如果方法名或实例变量名以单个下划线 _ 开始,则认为它是私有的。如果方法名或实例变量名以两个下划线 __ 开始,则它会被解释为另一个名称来防止和子类的方法或变量冲突,具体来说就是 __bar 会变为 _ClassName__bar

7 显示了单下划线和双下划线的区别。

Table 7: 用单下划线或双下划线都可表示私有成员
  访问方式 说明
_foo obj._foo 单下划线开始,则认为它是私有的,只是一种约定,在外部是可以访问的
__bar obj._ClassName__bar 双下划线开始也是一种关于私有的约定,且名称前会自动加上 _ClassName 来避免可能出来在子类中的名称冲突

下面以私有实例变量为例子介绍一下单个下划线和两个下划线的区别:

>>> class MyClass():
...     def __init__(self):
...         self._foo = "This is _foo"      # 实例变量以单下划线开始
...         self.__bar = "This is __bar"    # 实例变量以双下划线开始
...
>>> obj = MyClass()
>>> print(obj._foo)
This is _foo
>>> print(obj._MyClass__bar)
This is __bar
>>> print(obj.__bar)                        # 不能直接访问 __bar,要通过 obj._MyClass__bar 访问
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute '__bar'

参考:https://peps.python.org/pep-0008/#method-names-and-instance-variables

8. 模块和包

Python 允许你把函数定义或公共部分放入一个文件,然后在其他程中使用 import 将该文件作为一个模块导入。保存公共代码的文件名就是模块名。

# file : div.py
def divide(a,b):
  q = a/b         # If a and b are integers, q is an integer
  r = a - q*b
  return (q,r)

要在其它的程序中使用上面模块,可以这样:

import div
a, b = div.divide(2305, 29)

import 语句创建一个新的名字空间,该空间包含模块中所有定义对象的名称。要访问这个名字空间,把模块名作为一个前缀来使用这个模块内的对象,就像上边例子中那样 div.divide()

8.1. 给模块换个名字(as)

导入时给模块换个名字,可以使用关键字 as

import div as foo
a,b = foo.divide(2305,29)

8.2. 导入模块中指定对象到当前名称空间(from)

可以用 from 导入指定对象到当前名称空间,这样使用模块对象时就可以省略模块前缀了。

from div import divide
a,b = divide(2305,29)        # No longer need the div prefix

可以用星号(*)把一个模块中除下划线开头的所有内容导入到当前的名称空间中:

from div import *

不过,如果一个模块如果定义有列表 __all__ ,则 from module import * 语句只能导入 __all__ 列表中存在的对象。

# module: foo.py
__all__ = [ 'bar', 'spam' ]      # 定义使用 `*` 可以导入的对象

8.3. 查看模块中的可用函数和变量(dir)

dir 可查看模块中的可用函数和变量。如:

$ python3 -q
>>> import string
>>> dir(string)
['ChainMap', 'Formatter', 'Template', '_TemplateMetaclass', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_re', '_string', 'ascii_letters', 'ascii_lowercase', 'ascii_uppercase', 'capwords', 'digits', 'hexdigits', 'octdigits', 'printable', 'punctuation', 'whitespace']

8.4. 模块的__name__属性

每个模块都拥有 __name__ 属性,它是一个内容为模块名字的字符串。 最顶层的模块名称是__main__。命令行或是交互模式下程序都运行在__main__模块内部。 利用__name__属性,我们可以让同一个程序在不同的场合(单独执行或被导入)具有不同的行为,像下面这样做:

# 检查是单独执行还是被导入
if __name__ == '__main__':
  # Yes
  statements
else:
  # No (可能被作为模块导入)
  statements

8.5.

一个 Python 源文件就是一个 Python 模块。 如果 Python 源文件所在的目录中存在一个名为 __init__.py 的文件,则这个文件夹就称为包(Package),目录名就是包名。 文件 __init__.py 可以为空文件。

假设有下面目录结构:

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __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
              ...

当执行 import sound (这是导入包)时会加载文件 sound/__init__.py
当执行 import sound.effects (这是导入子包)时会加载文件 sound/__init__.pysound/effects/__init__.py
当执行 import sound.effects.echo (这是导入模块)时会加载文件 sound/__init__.pysound/effects/__init__.pysound/effects/echo.py

9. Python 标准库

9.1. Built-in 函数

8 是 Python 3 的内置函数。

Table 8: Python 3 built-it Functions
    Built-in Functions    
abs() dict() help() min() setattr()
all() dir() hex() next() slice()
any() divmod() id() object() sorted()
ascii() enumerate() input() oct() staticmethod()
bin() eval() int() open() str()
bool() exec() isinstance() ord() sum()
bytearray() filter() issubclass() pow() super()
bytes() float() iter() print() tuple()
callable() format() len() property() type()
chr() frozenset() list() range() vars()
classmethod() getattr() locals() repr() zip()
compile() globals() map() reversed() __import__()
complex() hasattr() max() round()  
delattr() hash() memoryview() set()  

参考:Python 3 Built-in Functions

9.2. 文本处理

9.2.1. 输入与输出

下面的程序打开一个文件,然后一行行地读出并显示文件内容(相当于 cat 命令):

import sys

f = open("foo.txt")
line = f.readline()
while line:
    sys.stdout.write(line)
    line = f.readline()
f.close()

9.3. argparser 模块(处理命令行参数)

optparse 模块已经过时,推荐使用 argparser 模块(它是在 Python 2.6 和 Python 3.2 中引入的)。

9.4. logging 模块

实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging

logging.basicConfig(
    format = "[%(filename)s:%(lineno)s:%(funcName)15s %(levelname)8s] %(message)s",
    level = logging.INFO
)
logger = logging.getLogger(__name__)


def fun1():
    # some things
    logger.critical("This is critical msg")
    logger.error("This is error msg")
    # some things


fun1()
logger.warning("This is warning msg")
logger.info("This is info msg")
logger.debug("This is debug msg")

9.5. struct 模块(处理二进制数据结构)

struct 用于在 Python 与二进制数据结构之间转换数据。这些数据结构通常在与使用 C 编写的函数,二进制文件格式、网络协议进行交互时使用。

下面是用于打包和解包的两个基本函数:

struct.pack(fmt, v1, v2, ...)   # 根据fmt中的格式字符串,将值v1,v2等打包后返回
struct.unpack(fmt, buffer)      # 把buffer中的二进制数据按fmt指定的格式进行解包后返回

9.5.1. 格式字符串

进行打包解包操作时,需要指定二进制数据的格式,它们可取表 9 中的字符。

Table 9: pack/unpack 的合法格式字符串(即参数 fmt)
Format C Type Python type Standard size
x pad byte no value  
c char bytes of length 1 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
n ssize_t integer  
N size_t integer  
e   float 2
f float float 4
d double float 8
s char[] bytes  
p char[] bytes  
P void * integer  

下面是把打包的例子:

>>> import struct
>>> struct.pack('b', 65)
'A'
>>> struct.pack('h', 65)
'A\x00'
>>> struct.pack('i', 65)
'A\x00\x00\x00'
>>> struct.pack('ii', 65, 66)
'A\x00\x00\x00B\x00\x00\x00'
>>> struct.pack('4s2s', b'abcd', b'ef')   # 对于s,需要在前面指定长度
'abcdef'

下面是解包二进制数据的例子:

>>> import struct
>>> struct.unpack('i', b'A\x00\x00\x00')
(65,)
>>> struct.unpack('ii', b'A\x00\x00\x00B\x00\x00\x00')
(65, 66)
>>> struct.unpack('bb', b'AB')
(65, 66)
>>> struct.unpack('4s2s', b'abcdef')
('abcd', 'ef')

如果是多字节的数据,我们可以在格式字符串的第一个字符(可指定为表 10 所示字符)指定大小端形式。

Table 10: 格式字符串第一个字符可指定大小端(省略时,默认为@)
Character Byte order Size Alignment
@ native(依赖于 sys.byteorder) native native
= native standard none
< little-endian standard none
> big-endian standard none
! network (= big-endian) standard none

如:

>>> struct.pack('i', 0x3)     # 没有明确指定大小端,默认为@(在intel平台中为小端形式)
'\x03\x00\x00\x00'
>>> struct.pack('<i', 0x3)    # 明确指定以小端形式编码
'\x03\x00\x00\x00'
>>> struct.pack('>i', 0x3)    # 明确指定以大端形式编码
'\x00\x00\x00\x03'

9.5.2. Struct 对象

如果使用同一个格式字符串进行多次打包解包操作,推荐使用 Struct 对象。它效率更高,因为需要解释一次格式字符串。如:

>>> s = struct.Struct('<ii')
>>> s.pack(0x3, 0x4)
'\x03\x00\x00\x00\x04\x00\x00\x00'
>>> s.pack(0x5, 0x6)
'\x05\x00\x00\x00\x06\x00\x00\x00'

9.5.3. struct.unpack 实例:分析 zip 文件头

下面是使用 struct.unpack 分析 zip 文件 header 的例子:

import struct

zipfile_path = '/path/to/file.zip'

# zip file header format (first 30 bytes):
# All multi-byte values in the header are stored in little-endian byte order.
# | Offset | Bytes | Description                                                               |
# |--------+-------+---------------------------------------------------------------------------|
# |      0 |     4 | Local file header signature = 0x04034b50 (read as a little-endian number) |
# |      4 |     2 | Version needed to extract (minimum)                                       |
# |      6 |     2 | General purpose bit flag                                                  |
# |      8 |     2 | Compression method                                                        |
# |     10 |     2 | File last modification time                                               |
# |     12 |     2 | File last modification date                                               |
# |     14 |     4 | CRC-32                                                                    |
# |     18 |     4 | Compressed size                                                           |
# |     22 |     4 | Uncompressed size                                                         |
# |     26 |     2 | File name length (n)                                                      |
# |     28 |     2 | Extra field length (m)                                                    |
#
# See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers

with open(zipfile_path, 'rb') as zipf:
    header = zipf.read(30)   # read first 30 bytes in zip
    result = struct.unpack('<Ihhhhhiiihh', header)

signature = result[0]
min_ver = result[1]
bit_flag = result[2]
cmp_method = result[3]
modif_time = result[4]
modif_date = result[5]
crc32 = result[6]
cmp_size = result[7]
uncmp_size = result[8]
fname_len = result[9]
extra_field_len = result[10]

print(result)
print("min_ver = {0}".format(min_ver))
print("modif_time = {0}".format(modif_time))
print("modif_date = {0}".format(modif_date))

9.6. enum 枚举

下面是 Python 中枚举的使用实例:

#!/usr/bin/env python3

from enum import Enum

class Color(Enum):                               # 创建枚举的方式 1
    RED = 0
    GREEN = 1
    BLUE = 2

# Color = Enum('Color', ['RED', 'GREEN', 'BLUE'])  # 创建枚举的方式 2

color = Color.RED

print(color.name)          # 每个枚举都有 name 属性
print(color.value)         # 每个枚举都有 value 属性

match color:               # python 3.10 引入了 match 语句
    case Color.RED:
        print("I see red!")
    case Color.GREEN:
        print("Grass is green")
    case Color.BLUE:
        print("I'm feeling the blues :(")

参考:https://docs.python.org/3/library/enum.html

9.7. venv 模块(创建虚拟环境)

有时,工程 1 中使用的包 A 依赖于包 B 的 1.0 版本;而工程 2 中使用的包 C 依赖于包 B 的 2.0 版本。而包 B 的 2.0 版本和 1.0 版本不兼容,我们无法在系统中同时安装这两个版本。这时,通过创建“虚拟环境”可以解决这个问题。

Python 3.3 中增加了内置模块 venv,可以用来创建“虚拟环境”,在这个环境中安装的包会保存在当前环境关联的目录中,而不是系统目录中。

venv 的基本用法如下:

$ python3 -m venv /path/to/new/virtual/environment        # 创建虚拟环境,需要指定新环境目录
$ ls /path/to/new/virtual/environment
bin/  include/  lib/  lib64@  pyvenv.cfg  share/
$ source /path/to/new/virtual/environment/bin/activate    # 进入虚拟环境

$ pip install xxx  # 进入虚拟环境后,安装的包都会放在的当前环境目录的子目录lib/pythonX.Y/中

$ deactivate                                              # 退出虚拟环境

注:virtualenv 是另一个流行的 Python 虚拟环境创建工具,它支持 Python 2 和 Python 3(前面介绍的 venv 只支持 Python 3)。

参考:
https://virtualenv.pypa.io/en/latest/
https://docs.python.org/3/library/venv.html

10. pip 基本使用

pip 是安装 Python 模块的实用工具。

参考:https://pip.pypa.io/en/stable/quickstart/

10.1. 安装软件(install)

$ pip install PackageName

10.1.1. 根据 requirements.txt 文件安装软件

$ pip freeze > requirements.txt                 # 把当前环境下安装的软件列表保存到requirements.txt中
$ pip install --requirement requirements.txt    # 安装 requirements.txt 文件中的所有软件
$ pip install -r requirements.txt               # 是上条命令的简写

下面是 requirements.txt 文件的一个例子

$ cat requirements.txt
cycler==0.10.0
functools32==3.2.3.post2
matplotlib==2.0.2
numpy==1.13.0
pyparsing==2.2.0
python-dateutil==2.6.0
pytz==2017.2
six==1.10.0
subprocess32==3.2.7
vboxapi==1.0

不一定要指定精确版本,也可以使用 >= 指定“最低版本”,如:

numpy>=1.13.0

10.1.2. 升级软件

$ pip install --upgrade PackageName      # 升级软件
$ pip install -U PackageName             # 是上条命令的简写

10.2. 卸载软件(uninstall)

$ pip uninstall PackageName

10.3. 查看已经安装的软件列表(list)

$ pip list
cycler (0.10.0)
functools32 (3.2.3.post2)
matplotlib (2.0.2)
numpy (1.13.0)
pip (9.0.1)
pyparsing (2.2.0)
python-dateutil (2.6.0)
pytz (2017.2)
setuptools (18.7.1)
six (1.10.0)
subprocess32 (3.2.7)
vboxapi (1.0)
wheel (0.26.0)

10.3.1. 查看过时的软件

$ pip list --outdated
numpy (1.13.0) - Latest: 1.13.1 [wheel]
setuptools (18.7.1) - Latest: 36.0.1 [wheel]
wheel (0.26.0) - Latest: 0.29.0 [wheel]

10.4. 查看某个包的信息(show)

$ pip show numpy
Name: numpy
Version: 1.13.0
Summary: NumPy: array processing for numbers, strings, records, and objects.
Home-page: http://www.numpy.org
Author: NumPy Developers
Author-email: numpy-discussion@python.org
License: BSD
Location: /usr/local/lib/python2.7/site-packages
Requires:

10.5. 在线查找软件包(search)

$ pip search json |more
json-rpc-3 (1.8.4)                                         - Pure Python 3 JSON-RPC 2.0 transport realisation
PyWaPa-3k (0.1.1)                                          - Python WhAtever Parser is a python markup converter from xml, json, yaml and ini to python dictionary. It allows also conversion between markup languages. Python 3 compatible version.
django-json-404-middleware (0.0.1)                         - Django middleware that returns 404s as JSON instead of html
......

11. Tips

11.1. 手动安装模块

Python Package Index 上有很多 python 模块。推荐使用工具 pip 来在线安装 python 模块。

下面介绍的是手动安装模块的典型过程。

Python Package Index 中下载想要安装的模块,假设文件名为 foo-1.0.tar.gz,解压后里面会有个 setup.py 文件,执行它可进行安装。比如:

$ gunzip -c foo-1.0.tar.gz | tar xf -    # unpacks into directory foo-1.0
$ cd foo-1.0
$ python setup.py install --prefix=/Users/cig01/soft/ # 安装到系统目录中不用指定 --prefix 参数

如果想要查看帮助信息,可以执行 python setup.py --help 。如:

$ python setup.py --help
Common commands: (see '--help-commands' for more)

  setup.py build      will build the package underneath 'build/'
  setup.py install    will install the package

Global options:
  --verbose (-v)      run verbosely (default)
  --quiet (-q)        run quietly (turns verbosity off)
  --dry-run (-n)      don't actually do anything
  --help (-h)         show detailed help message
  --no-user-cfg       ignore pydistutils.cfg in your home directory
  --command-packages  list of packages that provide distutils commands

Information display options (just display information, ignore any commands)
  --help-commands     list all available commands
  --name              print package name
  --version (-V)      print package version
  --fullname          print <package name>-<version>
  --author            print the author's name
  --author-email      print the author's email address
  --maintainer        print the maintainer's name
  --maintainer-email  print the maintainer's email address
  --contact           print the maintainer's name if known, else the author's
  --contact-email     print the maintainer's email address if known, else the
                      author's
  --url               print the URL for this package
  --license           print the license of the package
  --licence           alias for --license
  --description       print the package description
  --long-description  print the long package description
  --platforms         print the list of platforms
  --classifiers       print the list of classifiers
  --keywords          print the list of keywords
  --provides          print the list of packages/modules provided
  --requires          print the list of packages/modules required
  --obsoletes         print the list of packages/modules made obsolete

usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

参考:
Installing Python Modules (Legacy version): https://docs.python.org/2/install/

11.2. 列出本地安装的所有 python 模块

如何列出本地安装的所有 python 模块?
方法一,在 python 提示符中输入: help('modules') ,如:

>>> help('modules')

Please wait a moment while I gather a list of all available modules...

BaseHTTPServer      audioop             httplib             sched
Bastion             base64              ihooks              select
CGIHTTPServer       bdb                 imaplib             sets
ConfigParser        binascii            imghdr              sgmllib
Cookie              binhex              imp                 sha
DocXMLRPCServer     bisect              importlib           shelve
HTMLParser          bsddb               imputil             shlex
IN                  bz2                 inspect             shutil
MimeWriter          cPickle             io                  signal
PyQt4               cProfile            itertools           sip
Queue               cStringIO           json                sipconfig
SimpleHTTPServer    calendar            keyword             sipdistutils
SimpleXMLRPCServer  cgi                 lib2to3             site
SocketServer        cgitb               linecache           smtpd
StringIO            chunk               locale              smtplib
UserDict            cmath               logging             sndhdr
UserList            cmd                 macpath             socket
UserString          code                macurl2path         sqlite3
_LWPCookieJar       codecs              mailbox             sre
_MozillaCookieJar   codeop              mailcap             sre_compile
__builtin__         collections         markupbase          sre_constants
__future__          colorsys            marshal             sre_parse
_abcoll             commands            math                ssl
_ast                compileall          md5                 stat
_bisect             compiler            mhlib               statvfs
_bsddb              contextlib          mimetools           string
_codecs             cookielib           mimetypes           stringold
_codecs_cn          copy                mimify              stringprep
_codecs_hk          copy_reg            mmap                strop
_codecs_iso2022     crypt               modulefinder        struct
_codecs_jp          csv                 multifile           subprocess
_codecs_kr          ctypes              multiprocessing     sunau
_codecs_tw          curses              mutex               sunaudio
_collections        datetime            netrc               symbol
_csv                dbhash              new                 symtable
_ctypes             dbm                 nntplib             sys
_ctypes_test        dbus                ntpath              sysconfig
_curses             decimal             nturl2path          syslog
_curses_panel       difflib             numbers             tabnanny
_dbus_bindings      dircache            opcode              tarfile
_dbus_glib_bindings dis                 operator            telnetlib
_elementtree        distutils           optparse            tempfile
_functools          doctest             os                  termios
_hashlib            dsextras            os2emxpath          textwrap
_heapq              dumbdbm             parser              this
_hotshot            dummy_thread        pdb                 thread
_io                 dummy_threading     pickle              threading
_json               email               pickletools         time
_locale             encodings           pipes               timeit
_lsprof             errno               pkgutil             toaiff
_multibytecodec     exceptions          platform            token
_multiprocessing    fcntl               plistlib            tokenize
_osx_support        filecmp             popen2              trace
_pyio               fileinput           poplib              traceback
_random             fnmatch             posix               tty
_socket             formatter           posixfile           types
_sqlite3            fpformat            posixpath           unicodedata
_sre                fractions           pprint              unittest
_ssl                ftplib              profile             urllib
_strptime           functools           pstats              urllib2
_struct             future_builtins     pty                 urlparse
_symtable           gc                  pwd                 user
_sysconfigdata      gdbm                py_compile          uu
_testcapi           genericpath         pyclbr              uuid
_threading_local    getopt              pydoc               warnings
_warnings           getpass             pydoc_data          wave
_weakref            gettext             pyexpat             weakref
_weakrefset         gio                 pygtk               webbrowser
abc                 glib                quopri              whichdb
aifc                glob                random              wsgiref
antigravity         gobject             re                  xdrlib
anydbm              grp                 readline            xml
argparse            gzip                repr                xmllib
array               hashlib             resource            xmlrpclib
ast                 heapq               rexec               xxsubtype
asynchat            hmac                rfc822              zipfile
asyncore            hotshot             rlcompleter         zipimport
atexit              htmlentitydefs      robotparser         zlib
audiodev            htmllib             runpy

Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose descriptions contain the word "spam".

方法二,在 shell 提示符中输入: pydoc modules

参考:
http://stackoverflow.com/questions/739993/how-can-i-get-a-list-of-locally-installed-python-modules

11.3. 检测某个 python 模块是否安装和模块安装位置

用命令 python -c 'import modulename' 可以检测系统中是否安装有指定模块,如果正确安装了模块 modulename,那么这个命令不会有任何输出;否则会提示错误信息,如:

$ python -c 'import pycurl'             # 当前系统中没有模块pycurl,会提示类似下面的错误
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named pycurl

用命令 python -c 'import modulename; print(modulename.__file__)' 可以进一步查看模块在系统中的所在路径。如:

$ python -c 'import zlib; print(zlib.__file__)'             # 检测 zlib 所在路径
/usr/lib64/python2.6/lib-dynload/zlibmodule.so
$ python -c 'import json.tool; print(json.tool.__file__)'   # 检测 json.tool 所在路径
/usr/lib64/python2.6/json/tool.pyc

11.4. 一条命令实现 HTTP 服务

下面命令可实现一个 HTTP 服务,把当前目录设为 HTTP 的根目录,可以通过 http://localhost:8000 访问。

$ python -m SimpleHTTPServer          # Python 2,默认端口8000
$ python -m SimpleHTTPServer [port]   # Python 2,指定端口

如果你使用 Python 3,上面命令会报错,你可以执行:

$ python -m http.server               # Python 3,默认端口8000
$ python -m http.server [port]        # Python 3,指定端口

11.5. 提高 Python 代码质量

11.5.1. Python 代码风格(PEP8)

Python 有一套自己的代码风格。如有命名约定:py 脚本名,函数名,变量名等使用“全小写字母”的形式,要分隔的话中间使用“下划线”,如 lower_with_under.py;而对于类名,一般使用 CapitalizedWords 风格。

完整的代码风格可参考:PEP 8 -- Style Guide for Python Code

11.5.2. 使用 pycodestyle 检查代码是否符合 PEP8 规范

使用工具 pycodestyle (这个工具以前的名字是 pep8)可以检查代码是否符合 PEP8 规范。安装如下:

$ pip install pycodestyle

下面是 pycodestyle 的使用实例:

$ pycodestyle --show-source --show-pep8 testsuite/E40.py
testsuite/E40.py:2:10: E401 multiple imports on one line
import os, sys
         ^
    Imports should usually be on separate lines.

    Okay: import os\nimport sys
    E401: import sys, os

11.5.3. 代码格式化工具

11.5.3.1. autopep8

使用工具 autopep8 (它依赖于 pycodestyle)可以转换 Python 代码为符合 PEP8 规范的代码。安装如下:

$ pip install autopep8

下面是使用 autopep8 原地修改源码的实例:

$ autopep8 --in-place --aggressive --aggressive <filename>
11.5.3.2. yapf(推荐)

yapf 是 Google 出品的一个 Python 代码格式化工具,推荐使用。

11.5.4. 静态分析工具(pylint)

使用工具 pylint 可以检测 Python 代码中的一些潜在问题,如警告未使用的变量等等。

11.6. Python 3 和 Python 2 不兼容

Writing code that runs under both Python2 and 3: https://wiki.python.org/moin/PortingToPy3k/BilingualQuickRef
Python 3 和 Python 2 有很多不兼容的地方,请参考: The key differences between Python 2.7.x and Python 3.x with examples
编写和 Python 3 和 Python 2 都兼容的代码,请参考: Cheat Sheet: Writing Python 2-3 compatible code

Author: cig01

Created: <2014-11-09 Sun>

Last updated: <2021-01-13 Wed>

Creator: Emacs 27.1 (Org mode 9.4)