可以从 python.org(https://www.python.org/)网站下载 Python。但是对于没有安装过 Python 的读者,特别推荐安装 Anaconda 版本(https://www.continuum.io/downloads),这个版本涵盖了数据科学工作需要用到的大多数库。

当我写本书时,Python 的最新版本是 3.4。但是,在 DataSciencester,我们使用一个旧的可靠版本,Python 2.7。Python 3 无法向后兼容 Python 2,因此很多重要功能只能在 Python 2.7 上良好运行。数据科学社区中,2.7 版本一直是主流,我们也和它保持一致。请设法安装这个版本。

如果你没有 Anaconda 版本,那么需要安装 pip(https://pypi.python.org/pypi/pip),这是一个 Python 包管理器,可以用来方便地安装第三方包(其中有些是我们必需的)。IPython(http://ipython.org/)也不错,操作界面更友好。

(如果你已经安装了 Anaconda 版本,它会很好地和 pip 与 IPython 兼容。) 运行:

pip install ipython

如果碰到难解的错误信息,可以在网上搜索解决办法。

Python 的设计原则(http://legacy.python.org/dev/peps/pep-0020/)有着禅宗的意味,你输入 import this 就能在 Python 解释器中一窥玄机。

讨论最多的原则之一是:

应该有一个——且最好只有一个——明显的方式去完成工作。

按照这种“明显”的方式(对一个新人来说可能根本不明显)编写的代码常常被称为具有“Python 风格”。尽管本书不是专门介绍 Python 的书,但我们时不时地会比较 Python 风格和非 Python 风格的方式在解决相同问题时的差异,而且你常常会发现 Python 风格是更好的解决方式。

许多编程语言用大括号分隔代码块。Python 使用缩进的方式:

for i in [1, 2, 3, 4, 5]:
    print i                    # "for i"程序块的第一行
    for j in [1, 2, 3, 4, 5]:
        print j                # "for j"程序块的第一行
        print i + j            # "for j"程序块的最后一行
    print i                    # "for i"程序块的最后一行
print "done looping"

这样会使 Python 代码非常易读,但这也意味着你需要非常小心格式。系统会省略方括号和圆括号中的空白,这对冗长的计算非常有用:

long_winded_computation = (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20)

并能使代码更易读:

list_of_lists = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

easier_to_read_list_of_lists = [ [1, 2, 3],
                                 [4, 5, 6],
                                 [7, 8, 9] ]

你可以用反斜线代表一个语句在下一行续写,尽管这个例子并不常见:

two_plus_three = 2 + \
                 3

空白形式的一个后果是很难将代码复制并粘贴到 Python 的 shell。比如,如果你粘贴代码:

for i in [1, 2, 3, 4, 5]:

    # 注意这个空行
    print i

在粘贴入一般的 Python shell 后,会得到以下提示:

IndentationError: expected an indented block

因为解释器认为空行表示 for 循环的终结。

IPython 有一个奇妙的函数 %paste,可以正确地复制剪贴板上的内容,包括空白在内。这个函数足以成为选择使用 IPython 的好理由。

Python 的某些特征默认不加载,包含了语言本身的部分特征,也包含了需读者自行下载的第三方特征。为了使用这些特征,你需要导入包含这些特征的模块。

一种方式是简单地导入模块本身:

import re
my_regex = re.compile("[0-9]+", re.I)

这里的 re 代表包含了处理正则表达式需要的函数与常量的模块。输入 import 之后,你可以通过加前缀 re. 来直接调用模块中的函数。

如果你的代码中已经有了不同的 re,可以使用别名:

import re as regex
my_regex = regex.compile("[0-9]+", regex.I)

如果模块名冗长,或者你需要敲很多字符,那不妨试试这个方法。比如,你想对数据用模块 matplotlib 来进行可视化,标准转换如下:

import matplotlib.pyplot as plt

如果你需要一个模块中的一些特定值,可以显式导入,直接使用,不必提前获取权限,如下所示:

from collections import defaultdict, Counter
lookup = defaultdict(int)
my_counter = Counter()

如果你是破坏者,可以将模块的全部内容导入命名空间,这也许会不加提示地覆盖原先定义的变量:

match = 10
from re import *      # 呃,re有一个match函数
print match           # "<function re.match>"

但是,不是破坏者就别这样做。

Python 2.7 默认整除,因此 5 / 2 等于 2。但是这并不总是我们想要的,因此文件常常需要这样开始:

from __future__ import division

这样 5 / 2 等于 2.5。本书中的所有示例程序采用新除法。在少数需要整除的情形下,我们可以通过双斜线 5 // 2 表示。

函数是一种规则,输入零或者其他数,得到相应的输出。在 Python 中,我们用 def 定义函数:

def double(x):
    """this is where you put an optional docstring
    that explains what the function does.
    for example, this function multiplies its input by 2"""
    return x * 2

Python 函数是第一类函数,第一类函数意味着可以将它们赋给其他变量,也可以像其他参数一样传递给函数:

def apply_to_one(f):
    """calls the function f with 1 as its argument"""
    return f(1)

my_double = double             # 指向之前定义的函数
x = apply_to_one(my_double)    # 等于2

也很容易生成简短的匿名函数,或者 lambda:

y = apply_to_one(lambda x: x + 4)     # 等于5

你可以将 lambda 赋给变量,尽管大部分人会建议你用 def

another_double = lambda x: 2 * x      # 别这么做
def another_double(x): return 2 * x   # 要这么做

默认参数也可以赋值给函数参数,当你需要默认值以外的值时需要具体说明:

def my_print(message="my default message"):
    print message
my_print("hello")      # 打印"hello"
my_print()             # 打印"my default message"

有时候通过名字指定参数会有用:

def subtract(a=0, b=0):
    return a - b

subtract(10, 5) # 返回5
subtract(0, 5)  # 返回-5
subtract(b=5)   # 和前一句一样

我们可以生成很多很多函数。

字符串可以用单引号或者双引号标注分隔出来(但引号需要配对,即单引号对单引号,双引号对双引号):

single_quoted_string = 'data science'
double_quoted_string = "data science"

Python 用反斜线来为特殊字符编码。比如:

tab_string = "\t"    # 表示tab字符
len(tab_string)      # 是1

如果你希望反斜线仅仅代表反斜线本身(你在 Windows 系统中的文件夹或者正则表达式中也许会遇到),那么可以使用命令 r"" 生成一个原始的字符串:

not_tab_string = r"\t"    # 表示字符'\'和't'
len(not_tab_string)       # 是2

你可以通过三重[两重]引号来生成多行的字符串:

multi_line_string = """This is the first line.
and this is the second line
and this is the third line"""

当发生意外时,Python 会报出异常。如果不处理,异常会引起程序崩溃。你可以用 tryexcept 来解决:

try:
    print 0 / 0
except ZeroDivisionError:
    print "cannot divide by zero"

在许多语言中,异常意味着坏情形。但在 Python 中,你不必害怕遇见异常,只要能写出清晰的代码,我们时而会这样做。

Python 中最基本的数据结构是列表(list)。一个列表是一个有序的集合。(这和其他语言中的数组概念类似,但增加了函数功能。)

integer_list = [1, 2, 3]
heterogeneous_list = ["string", 0.1, True]
list_of_lists = [ integer_list, heterogeneous_list, [] ]

list_length = len(integer_list)      # 等于3
list_sum    = sum(integer_list)      # 等于6

你可以通过方括号对列表的第 n 个元素读值和赋值:

x = range(10)   # 是列表[0, 1, ..., 9]
zero = x[0]     # 等于0,列表是0-索引的
one = x[1]      # 等于1
nine = x[-1]    # 等于9,最后一个元素的Python惯用法
eight = x[-2]   # 等于8,倒数第二个元素的Pyhton惯用法
x[0] = -1       # 现在x是[-1, 1, 2, 3, ..., 9]

你也可以用方括号来“切取”列表:

first_three   = x[:3]              # [-1, 1, 2]
three_to_end = x[3:]               # [3, 4, ..., 9]
one_to_four = x[1:5]               # [1, 2, 3, 4]
last_three = x[-3:]                # [7, 8, 9]
without_first_and_last = x[1:-1]   # [1, 2, ..., 8]
copy_of_x = x[:]                   # [-1, 1, 2, ..., 9]

Python 可以通过操作符 in 确认列表成员:

1 in [1, 2, 3]    # True
0 in [1, 2, 3]    # False

确认每次都会遍历列表元素。这意味着除非列表很小,否则就不应该进行确认(除非你不在乎确认需要花费多长时间)。

将列表串连起来很容易:

x = [1, 2, 3]
x.extend([4, 5, 6])   # x现在是[1,2,3,4,5,6]

如果不希望改变原序列 x,你可以使用列表加法:

x = [1, 2, 3]
y = x + [4, 5, 6]    # y是[1, 2, 3, 4, 5, 6];x是不变的

更常见的做法是,一次在原列表上只增加一项:

x = [1, 2, 3]
x.append(0)       # x现在是[1, 2, 3, 0]
y = x[-1]         # 等于0
z = len(x)        # 等于4

如果你知道列表中元素的个数,可以方便地从中提取值:

x, y = [1, 2]    # 现在x是1,y是2

不过,如果等式两端元素个数不同,会报出提示 ValueError

如果你希望忽略某些值,常见的选择是使用下划线:

_, y = [1, 2]    # 现在y==2,不用关心第一个元素是什么

元组是列表的亲表哥。你对列表做的很多操作都可以对元组做,但不包括修改。元组通过圆括号(或者什么都不加)而不是方括号来给出具体的描述:

my_list = [1, 2]
my_tuple = (1, 2)
other_tuple = 3, 4
my_list[1] = 3     # my_list现在是[1, 3]

try:
    my_tuple[1] = 3
except TypeError:
    print "cannot modify a tuple"

元组是通过函数返回多重值的便捷方法:

def sum_and_product(x, y):
    return (x + y),(x * y)

sp = sum_and_product(2, 3)     # 等于(5,6)
s, p = sum_and_product(5, 10)  # s是15,p是50

元组(和列表)都可以进行多重赋值(multiple assignment):

x, y = 1, 2     # 现在x是1,y是2
x, y = y, x     # Python风格的互换变量,现在x是2,y是1

Python 中的另一种基本数据结构是字典,它将联系起来,让我们可以通过键快速找到对应值:

empty_dict = {}                         # Python风格
empty_dict2 = dict()                    # 更少的Python风格
grades = { "Joel" : 80, "Tim" : 95 }    # 字典

你也可以通过方括号查找键的值:

joels_grade = grades["Joel"]            # 等于80

如果你找的键不在字典中,会得到 KeyError 报错:

try:
    kates_grade = grades["Kate"]
except KeyError:
    print "no grade for Kate!"

你可以用 in 确认键的存在:

joel_has_grade = "Joel" in grades     # 正确
kate_has_grade = "Kate" in grades     # 错误

如果查找的键在字典中不存在,字典可以通过方法 get 返回默认值(而非报出异常):

joels_grade = grades.get("Joel", 0)   # 等于80
kates_grade = grades.get("Kate", 0)   # 等于0
no_ones_grade = grades.get("No One")  # 默认的默认值为None

你可以通过方括号来为键值对赋值:

grades["Tim"] = 99                    # 替换了旧的值
grades["Kate"] = 100                  # 增加了第三个记录
num_students = len(grades)            # 等于3

我们常常使用字典作为代表结构数据的简单方式:

tweet = {
    "user" : "joelgrus",
    "text" : "Data Science is Awesome",
    "retweet_count" : 100,
    "hashtags" : ["#data", "#science", "#datascience", "#awesome", "#yolo"]
}

除了查找特定的键,我们还可以查找所有值:

tweet_keys   = tweet.keys()      # 键的列表
tweet_values = tweet.values()    # 值的列表
tweet_items  = tweet.items()     # (键, 值)元组的列表

"user" in tweet_keys             # True,使用慢速的列表
"user" in tweet                  # 更符合Python惯用法,使用快速的字典
"joelgrus" in tweet_values       # True

字典的键不可改变,尤其是不能将列表作为键。如果你需要一个多维的键,应该使用元组或设法把键转换成字符串。

1. defaultdict

假设你需要计算某份文件中的单词数目。一个明显的方式是,建立一个键是单词、值是单词出现次数的字典。每次你查到一个单词,如果字典中存在这个词,就在该词的计数上增加 1,如果字典中没有这个词,就把这个词增加到这个字典中:

word_counts = {}
for word in document:
    if word in word_counts:
        word_counts[word] += 1
    else:
        word_counts[word] = 1

当查找缺失值碰到异常报出时,你可以遵循“与其瞻前顾后,不如果断行动”(Forgiveness is better than permission)的原则,果断处理异常:

word_counts = {}
for word in document:
    try:
        word_counts[word] += 1
    except KeyError:
        word_counts[word] = 1

第三种方法是使用 get,这种处理缺失值的方法比较优雅:

word_counts = {}
for word in document:
    previous_count = word_counts.get(word, 0)
    word_counts[word] = previous_count + 1

以上三种方法都略显笨拙,这是 defaultdict 的意义之所在。一个 defaultdict 相当于一个标准的字典,除了当你查找一个没有包含在内的键时,它用一个你提供的零参数函数建立一个新的键,并为它的值增加 1。为了使用 defaultdict,你需要将其从集合中导出:

from collections import defaultdict

word_counts = defaultdict(int)             # int()生成0

for word in document:
    word_counts[word] += 1

这对列表、字典或者你自己的函数都有用:

dd_list = defaultdict(list)              # list()生成一个空列表
dd_list[2].append(1)                     # 现在dd_list包含{2:[1]}

dd_dict = defaultdict(dict)              # dict()产生一个新字典
dd_dict["Joel"]["City"] = "Seattle"      # { "Joel" : { "City" : Seattle"}}

dd_pair = defaultdict(lambda: [0, 0])
dd_pair[2][1] = 1                        # 现在dd_pair包含{2: [0,1]}

当我们用字典“收集”某些键对应的结果,并且不希望每次查找某键是否存在都遍历一遍的时候,defaultdict 非常有用。

2. Counter

一个计数器将一个序列的值转化成一个类似于整型的标准字典(即 defaultdict(int))的键到计数的对象映射。我们主要用它来生成直方图:

from collections import Counter
c = Counter([0, 1, 2, 0])         # c是(基本的) { 0 : 2, 1 : 1, 2 : 1 }

这给我们提供了一个用来解决单词计数问题的很简便的方法:

word_counts = Counter(document)

一个 Counter 实例带有的 most_common 方法的例子如下:

# 打印10个最常见的词和它们的计数
for word, count in word_counts.most_common(10):
    print word, count

另一种数据结构是集合(set),它表示为一组不同的元素:

s = set()
s.add(1)         # s现在是1
s.add(2)         # s现在是{1,2}
s.add(2)         # s还是{1,2}
x = len(s)       # 等于2
y = 2 in s       # 等于True
z = 3 in s       # 等于False

我们使用集合的原因主要有两个。第一个是集合上有一种非常快速的操作:in。如果我们有大量的项目,希望对它的成分进行测试,那么使用集合比使用列表要合适得多:

stopwords_list = ["a","an","at"] + hundreds_of_other_words + ["yet", "you"]
"zip" in stopwords_list     # False,但需要检查每个元素

stopwords_set = set(stopwords_list)
"zip" in stopwords_set      # 非常快地检查

第二个原因是便于在一个汇总中寻找其中离散的项目:

item_list = [1, 2, 3, 1, 2, 3]
num_items = len(item_list)            # 6
item_set = set(item_list)             # {1, 2, 3}
num_distinct_items = len(item_set)    # 3
distinct_item_list = list(item_set)   # [1, 2, 3]

我们使用 set 的频率要远低于 dictlist

和大多数编程语言一样,你可以用 if 语句来执行一种有条件的行动:

if 1 > 2:
    message = "if only 1 were greater than two..."
elif 1 > 3:
    message = "elif stands for 'else if'"
else:
    message = "when all else fails use else (if you want to)"

也可以在一行语句中使用 if-then-else,我们有时候需要这么做:

parity = "even" if x % 2 == 0 else "odd"

Python 也有 while 循环:

x = 0
while x < 10:
    print x, "is less than 10"
    x += 1

尽管我们更常用 forin

for x in range(10):
    print x, "is less than 10"

如果你需要更复杂的逻辑表达式,可以使用 continuebreak

for x in range(10):
    if x == 3:
        continue        # 直接进入下次迭代
    if x == 5:
        break           # 完全退出循环
    print x

上面的语句会打印 0124

Python 的布尔数除了首字母大写之外,其他用法和大多数别的语言类似:

one_is_less_than_two = 1 < 2        # 等于True
true_equals_false = True == False   # 等于False

Python 使用 None 来表示一个不存在的值,它类似别的语言中的 null

x = None
print x == None    # 打印True,但这并非Python的惯用法
print x is None    # 打印True,符合Python的惯用法

Python 可以使用任何可被认为是布尔数的值。下面这些都是“假”(Falsy):

  • False

  • None

  • [ ](一个空 list

  • { }(一个空 dict

  • ""

  • set()

  • 0

  • 0.0

还有很多值可作为真(True)来处理。这样你可以很容易地使用 if 语句来对空列表、空字符串或空字典等进行测试。有时候,如果你没有意识到这种行为,会引入一些微妙的 bug:

s = some_function_that_returns_a_string()
if s:
    first_char = s[0]
else:
    first_char = ""

另一种简单的方法是:

first_char = s and s[0]

这是因为第一个值为“真”时,and 运算符返回它的第二个值,否则返回第一个值。类似地,如果 x 的取值可能是一个数也可能是 None,那么以下代码的结果就必然会是一个数字:

safe_x = x or 0

Python 还有一个 all 函数,它的取值是一个列表,当列表的每个元素都为真时返回 True

Python 还有一个 any 函数,当取值的列表中至少有一个元素为真时,它返回 True

all([True, 1, { 3 }])    # True
all([True, 1, {}])       # False,{}为假
any([True, 1, {}])       # True
all([])                  # True,列表里没有假的元素
any([])                  # False,列表里没有真的元素

30:00