# 九、加强您的 Python 基础 这些章节随附的代码示例不需要您精通 Python。 但是,他们将假定您之前至少已经掌握了 Python 脚本编写的基础知识。 他们还将特别假设您了解数据结构(例如列表和字典),并且对如何使类对象起作用具有想法。 如果您对上述主题没有信心或对 Python 语言了解甚少,建议您在开始阅读本书之前,先参加在线教程,例如[这个页面](http://www.codecademy.com/en/tracks/python),位于[这个页面](https://developers.google.com/edu/python/)的 Google Python 课程或 [Kaggle 提供的课程](https://www.kaggle.com/learn/python)。 所有课程都是免费的,并且在几个小时的学习中,它们应该为您提供所有基础知识,以确保您充分享受这本书。 如果您喜欢在一本书中学习 Python 基础知识,则可以阅读 [Jake Vanderplas](https://github.com/jakevdp/WhirlwindTourOfPython) 的《Python 旋风之旅》,您需要的 Python 基本知识:从变量赋值到导入包。 我们还准备了一些注释,这些注释在本简短但具有挑战性的奖金一章中进行了介绍,以突出显示重要性和增强您对 Python 语言对数据科学使用至关重要的所有方面的知识。 在本章中,您将学到以下内容: * 关于成为有效数据科学家的 Python 知识 * 通过观看视频学习 Python 的最佳资源 * 通过直接编写和测试代码来学习 Python 的最佳资源 * 通过阅读学习 Python 的最佳资源 # 您的学习列表 这是您需要学习像数据科学家一样熟练的基本 Python 数据结构。 除了实际的基础知识(数字,算术,字符串,布尔值,变量赋值和比较)之外,列表确实很短。 我们将通过仅涉及数据科学项目中的递归结构来简要地处理它。 请记住,这些主题颇具挑战性,但是如果您想编写有效的代码,则必须掌握这些主题: * 列表 * 字典 * 类,对象和面向对象的编程 * 异常 * 迭代器和生成器 * 条件 * 推导式 * 函数 根据您对 Python 语言的实际了解,将其作为复习或学习列表。 但是,请检查所有建议的示例,因为在本书学习过程中您将再次遇到它们。 # 列表 列表是元素的集合。 元素可以是整数,浮点数,字符串或一般对象。 此外,您可以将不同的类型混合在一起。 此外,列表比数组更灵活,因为数组仅允许单个数据类型。 要创建列表,可以使用方括号或`list()`构造器,如下所示: ```py a_list = [1, 2.3, 'a', True] an_empty_list = list() ``` 以下是一些在使用列表时需要记住的便捷方法: * 要访问第元素,请使用`[]`表示法: 请记住,列表从 0(零)开始索引; 也就是说,第一个元素的位置为 0。 ```py a_list[1] # prints 2.3 a_list[1] = 2.5 # a_list is now [1, 2.5, 'a', True] ``` * 您可以通过指出起点和终点(所得终点中不包括终点)来对列表进行切片,如下所示: ```py a_list[1:3] # prints [2.3, 'a'] ``` * 您可以使用冒号分隔的`start:end:skip`表示法对跳过进行切片,以便为每个跳过值获取一个元素,如下所示: ```py a_list[::2] # returns only odd elements: [1, 'a'] a_list[::-1] # returns the reverse of the list: [True, 'a', 2.3, 1] ``` * 要在列表末尾添加元素,可以使用`append()`: ```py a_list.append(5) # a_list is now [1, 2.5, 'a', True, 5] ``` * 要获取列表的长度,请使用`len()`函数,如下所示: ```py len(a_list) # prints 5 ``` * 要删除元素,请使用`del`语句,后跟要删除的元素: ```py del a_list[0] # a_list is now [2.5, 'a', True, 5] ``` * 要连接两个列表,请使用`+`,如下所示: ```py a_list += [1, 'b'] # a_list is now [2.5, 'a', True, 5, 1, 'b'] ``` * 您可以通过将列表分配给变量列表(或只是序列)而不是单个变量来解压缩列表: ```py a, b, c, d, e, f = [2.5, 'a', True, 5, 1, 'b'] # a now is 2.5, b is 'a' and so on ``` 请记住,列表是可变的数据结构; 您可以随时添加,删除和修改元素。 不可变列表称为元组,并用圆括号`(`和`)`表示,而不是列表`[`和`]`中的方括号: ```py tuple(a_list) # prints (2.5, 'a', True, 5, 1, 'b') ``` # 字典 **词典**是可以快速查找内容的表,因为每个键都与一个值相关联。 确实就像使用书的索引立即跳到您需要的内容。 键和值可以属于不同的数据类型。 键的唯一先决条件是它们应该是可散列的(这是一个相当复杂的概念;只需使键尽可能简单,因此,请勿尝试使用字典或列表作为键)。 要创建字典,可以使用大括号,如下所示: ```py b_dict = {1: 1, '2': '2', 3.0: 3.0} ``` 以下是使用字典时可以记住的一些便捷方法: * 要访问通过`k`键索引的值,请使用`[]`表示法,如下所示: ```py b_dict['2'] # prints '2' b_dict['2'] = '2.0' # b_dict is now {1: 1, '2': '2.0', 3.0: 3.0} ``` * 要插入或替换键的值,请再次使用`[]`表示法: ```py b_dict['a'] = 'a' # b_dict is now {3.0: 3.0, 1: 1, '2': '2.0', 'a': 'a'} ``` * 要获取字典中的元素数,请使用`len()`函数,如下所示: ```py len(b_dict) # prints 4 ``` * 要删除元素,请使用`del`语句,后跟要删除的元素: ```py del b_dict[3.0] # b_dict is now {1: 1, '2': '2.0', 'a': 'a'} ``` 请记住,字典(如列表)是可变的数据结构。 还要记住,如果您尝试访问其键不存在的元素,则会引发`KeyError`异常: ```py b_dict['a_key'] Traceback (most recent call last): File "", line 1, in KeyError: 'a_key' ``` 显而易见的解决方案是始终先检查字典中是否有元素: ```py if 'a_key' in b_dict: b_dict['a_key'] else: print("'a_key' is not present in the dictionary") ``` 否则,您可以使用`.get`方法。 如果键在字典中,则返回其值; 否则,不返回任何内容: ```py b_dict.get('a_key') ``` 最后,您可以使用集合模块中的数据结构`defaultdict`,它将永远不会引发`KeyError`,因为它是由不带任何参数,并为它可能想要的任何不存在的键提供默认值的函数实例化的: ```py from collections import defaultdict c_dict = defaultdict(lambda: 'empty') c_dict['a_key'] # requiring a nonexistent key will always return the string 'empty' ``` 可以使用`def`或`lambda`命令定义`defaultdict`使用的`default`函数,如下节所述。 # 定义函数 函数是指令的集合,通常会从您那里接收特定的输入,并提供与这些输入相关的一组特定的输出。 您可以将它们定义为单线,如下所示: ```py def half(x): return x/2.0 ``` 您还可以通过以下方式将它们定义为一组许多指令: ```py import math def sigmoid(x): try: return 1.0 / (1 + math.exp(-x)) except: if x < 0: return 0.0 else: return 1.0 ``` 最后,您可以使用`lambda`函数来创建匿名函数。 将匿名函数视为简单函数,您可以在代码中的任何位置内联定义,而无需使用`verbose`构造器(以`def`开头的函数)。 只需调用`lambda`,然后输入其输入参数即可; 然后,冒号将发信号通知要由`lambda`函数执行的命令的开始,这些命令必须在同一行上。 (没有`return`命令!这些命令将从`lambda`函数返回。)您可以将`lambda`函数用作另一个函数的参数,如先前在`defaultdict`中看到的那样,也可以使用它在一行中表达一个函数。 在我们的示例中就是这种情况,我们通过返回`lambda`函数并结合第一个参数来定义函数: ```py def sum_a_const(c): return lambda x: x+c sum_2 = sum_a_const(2) sum_3 = sum_a_const(3) print(sum_2(2)) print(sum_3(2)) # prints 4 and 5 ``` 要调用一个函数,请写上函数名称,然后在括号内写上其参数: ```py half(10) # prints 5.0 sigmoid(0) # prints 0.5 ``` 通过使用函数,您可以通过对重复过程进行形式化的输入和输出来使重复过程组合在一起,而不会让它们的计算以任何方式干扰主程序的执行。 实际上,除非您声明变量是全局变量,否则将丢弃您在函数中使用的所有变量,并且主程序将仅接收`return`命令返回的内容。 顺便说一句,请注意,如果您将一个列表传递给一个仅函数的列表,而该列表不会在变量中发生,那么即使不返回,也将对其进行修改,除非您将其复制。 为了复制列表,您可以使用复制或深层复制功能(要从复制包中导入),也可以仅使用应用于列表的运算符`[:]`。 为什么会这样? 因为列表尤其是由地址而不是整个对象引用的数据结构。 因此,当您将列表传递给函数时,您只是将一个地址传递给计算机的内存,该函数将通过修改您的实际列表来对该地址进行操作: ```py a_list = [1,2,3,4,5] def modifier(L): L[0] = 0 def unmodifier(L): M = L[:] # Here we are copying the list M[0] = 0 unmodifier(a_list) print(a_list) # you still have the original list, [1, 2, 3, 4, 5] modifier(a_list) print(a_list) # your list have been modified: [0, 2, 3, 4, 5] ``` # 类,对象和面向对象的编程 类是方法和属性的集合。 简而言之,属性是对象的变量(例如,`Employee`类的每个实例都有其自己的`name`,`age`,`salary`和`benefits`;它们都是属性)。 方法只是修改属性的简单函数(例如,设置员工姓名,设置他/她的年龄以及从数据库或 CSV 列表中读取此信息)。 要创建一个类,请使用`class`关键字。 在下面的示例中,我们将为增量器创建一个类。 此对象的目的是跟踪整数值,并最终将其增加 1: ```py class Incrementer(object): def __init__(self): print ("Hello world, I'm the constructor") self._i = 0 ``` `def`缩进中的所有内容都是`class`方法。 在这种情况下,名为`__init__`的方法会将`i`内部变量设置为零(它看起来就像上一章中描述的函数一样)。 仔细查看方法的定义。 它的参数是`self`(这是对象本身),每个内部变量的访问都通过`self`进行: 1. `__init__`不仅仅是一种方法; 它是构造器(在创建对象时调用)。 实际上,当我们构建`Increment`对象时,将自动调用此方法,如下所示: ```py i = Incrementer() # prints "Hello world, I'm the constructor" ``` 2. 现在,让我们创建`increment()`方法,该方法将增加`i`内部计数器并返回状态。 在类定义中,包括方法: ```py def increment(self): self._i += 1 return self._i ``` 3. 然后,运行以下代码: ```py i = Incrementer() print (i.increment()) print (i.increment()) print (i.increment()) ``` 4. 上面的代码产生以下输出: ```py Hello world, I'm the constructor 1 2 3 ``` 最后,让我们看看如何创建接受参数的方法。 现在,我们将创建`set_counter`方法,该方法设置`_i`内部变量: 1. 在类定义中,添加以下代码: ```py def set_counter(self, counter): self._i = counter ``` 2. 然后,运行以下代码: ```py i = Incrementer() i.set_counter(10) print (i.increment()) print (i._i) ``` 3. 上面的代码给出以下输出: ```py Hello world, I'm the constructor 11 11 ``` 请注意前面代码的最后一行,您可以在其中访问内部变量。 请记住,在 Python 中,默认情况下对象的所有内部属性都是公共的,并且可以在外部读取,写入和更改它们。 # 异常 异常和错误密切相关,但是它们是不同的东西。 例如,可以很好地处理异常。 以下是一些异常示例: ```py 0/0 Traceback (most recent call last): File "", line 1, in ZeroDivisionError: integer division or modulo by zero len(1, 2) Traceback (most recent call last): File "", line 1, in TypeError: len() takes exactly one argument (2 given) pi * 2 Traceback (most recent call last): File "", line 1, in NameError: name 'pi' is not defined ``` 在此示例中,提出了三个不同的异常(请参见每个块的最后一行)。 要处理异常,可以通过以下方式使用`try...except`块: ```py try: a = 10/0 except ZeroDivisionError: a = 0 ``` 您可以使用一个以上的`except`子句来处理多个异常。 您最终可以使用最终的`all-the-other`异常处理器。 在这种情况下,结构如下: ```py try: except KeyError: print ("There is a KeyError error in the code") except (TypeError, ZeroDivisionError): print ("There is a TypeError or a ZeroDivisionError error in the code") except: print ("There is another error in the code") ``` 最后,重要的是要提到有最后一个子句`finally`,它将在所有情况下执行。 如果您要清理代码(关闭文件,取消分配资源等),这非常方便。 这些都是应该独立完成的事情,无论是否发生错误。 在这种情况下,代码采用以下形状: ```py try: except: finally: ``` # 迭代器和生成器 遍历列表或字典非常简单。 请注意,对于字典而言,迭代是基于键的,下面的示例对此进行了演示: ```py for entry in ['alpha', 'bravo', 'charlie', 'delta']: print (entry) # prints the content of the list, one entry for line a_dict = {1: 'alpha', 2: 'bravo', 3: 'charlie', 4: 'delta'} for key in a_dict: print (key, a_dict[key]) # Prints: # 1 alpha # 2 bravo # 3 charlie # 4 delta ``` 另一方面,如果您需要遍历序列并快速生成对象,则可以使用生成器。 这样做的一个很大好处是,您不必在一开始就创建和存储完整的序列。 取而代之的是,每次调用生成器时,都会构建每个对象。 作为一个简单的示例,让我们为数字序列创建一个生成器,而无需事先存储完整列表: ```py def incrementer(): i = 0 while i<5: yield(i) i +=1 for i in incrementer(): print (i) # Prints: # 0 # 1 # 2 # 3 # 4 ``` # 条件 因为您可以分支程序,所以条件常用于数据科学中。 最常用的是`if`语句。 它的工作原理与其他编程语言大致相同。 这是一个例子: ```py def is_positive(val): if val< 0: print ("It is negative") elif val> 0: print ("It is positive") else: print ("It is exactly zero!") is_positive(-1) is_positive(1.5) is_positive(0) # Prints: # It is negative # It is positive # It is exactly zero! ``` 用`if`检查第一个条件。 如果还有其他条件,则用`elif`定义(代表`else...if`)。 最后,默认行为由`else`处理。 注意,`elif`和`else`不是必需的。 # 列表和字典的推导式 使用通过迭代器和条件构建为一行的推导式,列表和字典: ```py a_list = [1,2,3,4,5] a_power_list = [value**2 for value in a_list] # the resulting list is [1, 4, 9, 16, 25] filter_even_numbers = [value**2 for value in a_list if value % 2 == 0] # the resulting list is [4, 16] ``` ```py another_list = ['a','b','c','d','e'] a_dictionary = {key:value for value, key in zip(a_list, another_list)} # the resulting dictionary is {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4} ``` `zip`是一个函数,该函数将多个相同长度的列表作为输入,并同时遍历具有相同索引的每个元素,因此您可以将每个列表的前几个元素匹配在一起,依此类推。 理解是过滤和转换迭代器中存在的数据的快速方法。 # 通过观看,阅读和实践来学习 如果进修课程和我们的学习列表还不够,您需要更多支持以增强您的 Python 知识怎么办? 我们将建议您在网上免费获得更多资源。 通过观看教程视频,您可以尝试复杂且不同的示例,并通过一项艰巨的任务挑战自己,该任务需要您与其他数据科学家和 Python 专家进行互动。 # 大规模公开在线课程(MOOC) 近年来,MOOC 变得越来越流行,在其在线平台上免费提供来自世界各地最好的大学和专家的一些最好的课程。 您将在 [Coursera](https://www.coursera.org/),[Edx](https://www.edx.org/) 和 [Udacity](https://www.udacity.com)。 另一个很好的来源是 MIT 开放式课件,[它很容易访问](http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-00sc-introduction-to-computer-science-and-programming-spring-2011/)。 当您访问这些站点中的每一个时,您可能会发现有关 Python 的其他有效课程。 我们建议 Google Inc.研究总监 Peter Norvig 提供免费的,始终可用的,自己动手的进度课程。该课程旨在使您对 Python 的了解达到更高的水平。 # PyCon 和 PyData **Python 大会**(**PyCon**)是在世界各地举办的年度大会,旨在促进 Python 语言的使用和传播。 在此类会议期间,通常会举行教程,动手演示和培训课程。 您可以查看[这个页面](http://www.pycon.org/),了解在您附近举行下一次 PyCon 的地点和时间。 如果您无法参加,则仍然可以在[这个页面](http://www.youtube.com)上进行搜索,因为大多数有趣的会话均已记录并上传到那里。 无论如何,参加和观看真实的演示是另一回事,所以我们热烈建议您参加这样的会议,因为这确实值得。 类似地,**PyData** 是致力于数据分析的 Python 开发人员和用户社区,在世界各地组织了许多活动。 您可以查看[这个页面](http://pydata.org/events.html)即将发生的事件,并查看是否有任何以往的事件对您感兴趣。 与 PyCon 一样,通常可以在 YouTube 上的 PyDataTV 等专用频道上进行演示。 # 交互式 Jupyter 有时,您需要一些书面说明,并有机会自己测试一些示例代码。 Jupyter 是一个类似 Python 本身的开放工具,可通过其笔记本(交互式网页)为您提供所有这些功能,您可以在其中找到说明和可以直接测试的示例代码。 我们在整本书中都对 Jupyter 及其内核进行了说明,因为它是真正的数据科学。 它使您可以轻松地运行 Python 脚本,并评估它们对正在处理的数据的影响。 IPython 内核(Jupyter 的 Python 内核,因为 Jupyter 可以运行许多不同的编程语言)的 GitHub 位置提供了示例笔记本的完整列表。 您可以在[这个页面](https://github.com/ipython/ipython/wiki/A-gallery-of-interesting-IPython-Notebooks)上进行检查。 特别是,列表的一部分与**通用 Python 编程**有关,而另一部分与**统计,机器学习和数据科学**有关,您会在其中找到很多 Python 脚本的示例,您可以从中学到灵感。 # 别害羞,接受真正的挑战 如果您想做一些可以使您的 Python 编码能力达到不同水平的事情,我们建议您去挑战 Kaggle。 [**Kaggle**](http://www.kaggle.com) 是用于预测建模和分析竞赛的平台,在数据中应用了竞争性编程(参与者尝试根据提供的规范进行编程)的思想,向参与者提出具有挑战性的数据问题,并要求他们提供可能在测试集上评估的解决方案,从而实现科学。 测试集的结果部分是公开的,部分是私有的。 对于 Python 学习者来说,最有趣的部分是有机会参加没有明显解决方案的实际问题,这需要您编写一些代码以提出可能的问题解决方案,甚至是简单或幼稚的解决方案(我们建议您从头开始) 参与复杂的解决方案之前)。 这样,学习者将发现有趣的教程,基准测试代码,有用的数据科学家社区,以及其他数据科学家或 [Kaggle](http://blog.kaggle.com/) 本身在其博客中提出的一些非常聪明的解决方案。 您可能想知道如何为自己找到正确的挑战。 只需在[这个页面](https://www.kaggle.com/competitions)上查看过去和现在的比赛,并寻找每一个有知识作为奖励的比赛。 您会惊讶地发现一个理想的阶段来学习其他数据科学家如何用 Python 进行编码,并且您可以立即应用从本书中学到的知识。