00章讲怎么安装Python3,这就大可不必看了;01章讲了Python3和Python2的差异,我之前没有学习过Python2,因此也直接跳过。
因此我会从02章开始阅读。17A也没有必要读。最后的17C很有意思,推荐了一些接下来的读物。
第一段话,时间不断改变,人也会跟着改变。没什么意义的一段话。这本来就是一个不断变化的世界,万事万物都在不断变化之中。
第二段话,迷墙是一部电影,没看过。这段话大概可以理解为Python3其实还是基于Python2的,学习方法差不多。
第三段话,中文直译大概是“不要把你的负担埋葬在圣洁的沉默中,你有问题吗?”。无法理解其中含义。
'''
suffiexes为后缀的意思。{}表示一个对象,1000和1024为键,他们的值都是数组,区别仅在于一个带i一个不带
我们知道,计算机的进制是1024,比如1MB = 1024KB。可以推测,上面的1000是一个粗略的进制计算,下面的是精确的。
'''
SUFFIXES = {
1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
}
'''
这是一个方法,或者说是函数。
approximate_size表示大致的尺寸;参数1是size,表示字节数量;a_kilobyte_is_1024_bytes,英文直译1KB = 1024B。
看来在这个程序大概是想要实现给定一个字节数量,根据粗略或者精确的进制将其转换为人类可读的形式
'''
def approximate_size(size, a_kilobyte_is_1024_bytes = True):
'''Convert a file size to human-readable form.
Keyword arguments
:param size: file size in bytes
:param a_kilobyte_is_1024_bytes: if True(default), use multiples of 1024. if False, user multiples of 1000
:return: string
'''
if size < 0: # 如果给定的文件大小小于0,抛出一个值异常,里面英文的含义是“数字必须为非负数”
raise ValueError('number must be non-negative')
multiple = 1024 if a_kilobyte_is_1024_bytes == True else 1000 # 这是Python独有的写法,大致含义是如果参数是True,倍数为1024,否则为1000
for suffix in SUFFIXES[multiple]: # 遍历对应倍数的数组。不断用文件大小除以倍数,如果文件大小已经小于倍数了,输出此时的文件大小和倍数
size /= multiple
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix)
raise ValueError('number too large') # 如果遍历结束了还是没有返回,代表数字太大了,我们预留的YB都不够
if __name__ == '__main__':
print(approximate_size(1000000000000))
print(approximate_size(1000000000000, False))
首先谈谈我的理解:这个程序实现的是将一个指定的文件大小转换为一个人类易读的形式,比如说1000000000000Byte表示为1.0TB。当然这个数值肯定是粗略的,因为余数都被舍弃了。有两种异常情况,首先是文件大小小于0会抛出,然后文件大小过大,大到无法表示也会抛出异常。对于程序中一些细节的描述,我已经在注释中给出。
接下来,是书中给出的解释。
当你需要一个函数的时候,声明它即可。函数声明以def开头,紧跟着函数的名称,然后是用括号括起来的参数,多个参数之间以逗号分隔。
同时注意,函数不定义返回值类型,但是每一个函数都有返回值。如果函数没有显示声明return语句,那么就会返回None。
参数类型也不会指定类型,Python会在内部自动计算出一个函数的类型并进行跟踪。
Python允许函数参数有默认值。如果函数被调用的时候没有指定参数,那么参数将使用默认值。不仅如此,通过使用命名参数还可以按照任何顺序指定参数。
if __name__ == '__main__':
print(approximate_size(1000000000000, False)) ①
print(approximate_size(1000000000000) ②
① 这个对 approximate_size() 函数的调用指定了两个参数。在approximate_size() 函数里面,a_kilobyte_is_1024_bytes 的值将为 False,因为你显式的传入了 False 作为第二个参数。
② 这个对 approximate_size() 函数的调用只指定了一个参数。但这是可以的,因为第二个参数是可选的!由于调用者没有指定,第二个参数就会使用在函数声明的时候定义的默认值True。
你也可以通过名称将值传入一个函数。
>>> from humansize import approximate_size
>>> approximate_size(4000, a_kilobyte_is_1024_bytes=False) ①
'4.0 KB'
>>> approximate_size(size=4000, a_kilobyte_is_1024_bytes=False) ②
'4.0 KB'
>>> approximate_size(a_kilobyte_is_1024_bytes=False, size=4000) ③
'4.0 KB'
>>> approximate_size(a_kilobyte_is_1024_bytes=False, 4000) ④
File "" , line 1
SyntaxError: non‐keyword arg after keyword arg
>>> approximate_size(size=4000, False) ⑤
File "" , line 1 SyntaxError: non‐keyword arg after ke
① 这个对 approximate_size() 函数的调用给第一个参数(size)指定了值 4000,并且给名为
a_kilobyte_is_1024_bytes 的参数指定了值 False。(那碰巧是第二个参数,但这没有关系,
马上你就会了解到。)
② 这个对 approximate_size() 函数的调用给名为 size 参数指定了值 4000,并为名为
a_kilobyte_is_1024_bytes 的参数指定了值 False。(这些命名参数碰巧和函数声明时列出
参数顺序一样,但同样不要紧。)
③ 这个对 approximate_size() 函数的调用给名为a_kilobyte_is_1024_bytes 的参数指定了值
False,然后给名为size 的参数指定了值 4000。(看到了没?我告诉过你顺序没有关系。)
④ 这个调用会失败,因为你在命名参数后面紧跟了一个非命名(位置的)的参数,这个一定
会工作。从左到右的读取参数列表,一旦你有一个命名的参数,剩下的参数也必须是命名的。
⑤ 这个调用也会失败,和前面一个调用同样的原因。 是不是很惊讶?别忘了,你给名为 size
的参数传入了值 4000,那么“显然的” False 这个值意味着对应了
a_kilobyte_is_1024_bytes 参数。但是 Python 不按照这种方式工作。
只要你有一个命名参数,右边的所有参数也必须是命名参数。
我不会长期指手划脚的来烦你,解释给你的代码添加文档注释的重要性。只要知道代码被编写一次但是会被阅读很多次,而且你的代码最主要的读者就是你自己,在编写它的六个月以后(例如,当你忘记了所有的东西但是又需要去修正一些东西的时候)。 Python使得编写易读的代码非常容易,因此要利用好这个优势。六个月以后你将会感谢我。
文档字符串
你可以通过使用一个文档字符串(简称 docstring )的方式给 Python 添加文档注释。在这个程序中,这个 approximate_size() 函数有一个 docstring:
'''Convert a file size to human-readable form.
Keyword arguments
:param size: file size in bytes
:param a_kilobyte_is_1024_bytes: if True(default), use multiples of 1024. if False, user multiples of 1000
:return: string
'''
每个函数都值得有一个合适的 docstring (文档字符串)。
三重引号表示一个多行的字符串。在开始引号和结束引号之间的所有东西都属于一个单独的字符串的一部分,包括回车、前导空格、和其他引号字符。你可以在任何地方使用它们,但是你会发现大部分时候它们在定义 docstring (文档注释)的时候使用。
三重引号之间的所有东西都是这个函数的 docstring (文档字符串),用来用文档描述这个函数是做什么的。一个 docstring (文档字符串),如果有的话,必须是一个函数里面定义的第一个东西(也就是说,紧跟着函数声明的下一行)。 你不需要严格的给你的每个函数提供一个 docstring (文档字符串),但大部分时候你总是应该提供。我知道你在曾经使用过的每一种程序语言里面听说过这个,但是 Python 给你提供了额外的诱因:这个 docstring (文档字符串)就像这个函数的一个属性一样在运行时有效。
很多 Python 的集成开发环境(IDE)使用 docstring (文档字符串)来提供上下文敏感的文档,以便于当你输入一个函数名称的时候,它的 docstring 会以一个提示文本的方式显式出来。这可能会极其有用,但它只有在你写出好的 docstring (文档字符串)的时候才有用。
在进一步讲解之前,我想简要的说一下库的搜索路径。当你试图导入(import)一个模块的时候,Python 会寻找几个地方。具体来说,它会搜寻在 sys.path 里面定义的所有目录。这只是一个列表,你可以容易地查看它或者使用标准的列表方法去修改它。(在内置数据类型你会了解更多关于列表的信息。)
>>> import sys ①
>>> sys.path ②
['', 'D:\\Python\\python37.zip', 'D:\\Python\\DLLs', 'D:\\Python\\lib', 'D:\\Python', 'C:\\Users\\31592\\AppData\\Roaming\\Python\\Python37\\site-packages', 'D:\\Python\\lib\\site-packages', 'D:\\Python\\lib\\site-packages\\requests-2.22.0-py3.7.egg', 'D:\\Python\\lib\\site-packages\\certifi-2019.6.16-py3.7.egg', 'D:\\Python\\lib\\site-packages\\urllib3-1.25.3-py3.7.egg', 'D:\\Python\\lib\\site-packages\\idna-2.8-py3.7.egg', 'D:\\Python\\lib\\site-packages\\chardet-3.0.4-py3.7.egg', 'D:\\Python\\lib\\site-packages\\win32', 'D:\\Python\\lib\\site-packages\\win32\\lib', 'D:\\Python\\lib\\site-packages\\Pythonwin']
>>> sys ③
<module 'sys' (built‐in)>
>>> sys.path.insert(0, '/home/mark/diveintopython3/examples') ④
>>> sys.path ⑤
['/home/mark/diveintopython3/examples', '', 'D:\\Python\\python37.zip', 'D:\\Python\\DLLs', 'D:\\Python\\lib', 'D:\\Python', 'C:\\Users\\31592\\AppData\\Roaming\\Python\\Python37\\site-packages', 'D:\\Python\\lib\\site-packages', 'D:\\Python\\lib\\site-packages\\requests-2.22.0-py3.7.egg', 'D:\\Python\\lib\\site-packages\\certifi-2019.6.16-py3.7.egg', 'D:\\Python\\lib\\site-packages\\urllib3-1.25.3-py3.7.egg', 'D:\\Python\\lib\\site-packages\\idna-2.8-py3.7.egg', 'D:\\Python\\lib\\site-packages\\chardet-3.0.4-py3.7.egg', 'D:\\Python\\lib\\site-packages\\win32', 'D:\\Python\\lib\\site-packages\\win32\\lib', 'D:\\Python\\lib\\site-packages\\Pythonwin']
① 导入 sys 模块,使它的所有函数和属性可以被使用。
② sys.path 是一个目录名称的列表,它构成了当前的搜索路径。(你会看到不一样的结果,这取决于你的操作系统,你正在运行的 Python 的版本,以及它原来被安装的位置。) Python会从头到尾的浏览这些目录(按照这个顺序),寻找一个和你正要导入的模块名称匹配的 .py 文件。
③ 其实,我说谎了。真实情况比那个更加复杂,因为不是所有的模块都按照 .py 文件来存储。有些,比如 sys 模块,属于内置模块(built‐in modules), 他们事实上被置入到 Python 本身里面了。 内置模块使用起来和常规模块一样,但是无法取得它们的 Python 源代码,因为它们不是用 Python 写的!( sys 模块是用 C 语言写的。)
④ 通过添加一个目录名称到 sys.path 里,你可以在运行时添加一个新的目录到 Python 的搜索路径中,然后无论任何时候你想导入一个模块,Python 都会同样的去查找那个目录。只要Python 在运行,都会一直有效。
⑤ 通过使用 sys.path.insert(0, new_path),你可以插入一个新的目录到 sys.path 列表的第一项,从而使其出现在 Python搜索路径的开头。这几乎总是你想要的。万一出现名字冲突(例如,Python 自带了版本 2 的一个特定的库,但是你想使用版本 3),这个方法就能确保你的模块能够被发现和使用,替代 Python 自带的版本。(注:可以通过sys.path.remove(’/home/mark/diveintopython3/examples’)删除添加的路径。)
假如你还不了解,我重复一下,我刚刚说过 Python 函数有属性,并且那些属性在运行时是可用的。一个函数,就像 Python 里面所有其他东西一样,是一个对象。
运行交互式的 Python Shell,按照下面的执行:
>>> import humansize ①
>>> print(humansize.approximate_size(4096, True)) ②
4.0 KiB
>>> print(humansize.approximate_size.__doc__) ③
'Convert a file size to human-readable form.\n Keyword arguments\n :param size: file size in bytes\n :param a_kilobyte_is_1024_bytes: if True(default), use multiples of 1024. if False, user multiples of 1000\n :return: string\n '
① 第一行导入了作为一个模块的 humansize 程序 — 我们可以交互式的使用的一大块代码,或者来自于一个更大的 Python 程序。一旦你导入了一个模块,你就可以引用它的任何公有的函数、类、或者属性。模块可以通过这种方式访问其他模块的功能,同样的你也可以在 Python 交互式的 Shell 里面做这样的事情。这是一个重要的概念,贯穿这本书,你会看到更多的关于它的内容。
② 当你想使用在导入的模块中定义的函数的时候,你需要包含模块的名称。因此你不能仅仅指明 approximate_size,它必须是 humansize.approximate_size 才行。如果你曾经使用过 Java里面的类,你就会依稀的感觉到这种方式比较熟悉。
③ 除了按照你期望的方式调用这个函数,你查看了这个函数的其中一个属性: doc(doc前后都有两个连续的下划线,参见代码。markdown不显示这些。)。
什么是一个对象?
Python 里面的所有东西都是对象,所有东西都可以有属性和方法。所有函数都有一个内置的属性 doc,用来返回这个函数的源代码里面定义的文档字符串(docstring)。 sys 模块是一个对象,它有(除了别的以外)一个名叫 path 的属性,等等。
不过,这还是没有回答这个更基础的问题:什么是一个对象?不同的程序语言用不同的方式定义了“对象”。在有些地方,它意味着所有的对象必须要有属性和方法;在另一些地方,它意味着所有的对象都是可衍生(可以创建子类)的。在 Python 里面,定义更加宽松。有些对象既没有属性也没有方法,然而它可以有。不是所有的对象都是可衍生的。但是,所有的东西都是对象,从这个意义上说,它能够被赋值到一个变量或者作为一个参数传入一个函数。
你可能从其他程序语言环境中听说过 “first‐class object” 的说法。在 Python 中,函数是 first‐class objects,你可以将一个函数作为一个参数传递给另外一个函数;模块是 first‐class objects,你可以把整个模块作为一个参数传递给一个函数;类是 first‐class objects,而且类的单独的实例也是 first‐class objects。
一等对象 first-class object(第一类对象)有如下“特权”:
可以被赋值给一个变量
可以嵌入到数据结构中
可以作为参数传递给函数
可以作为值被函数返回
这个很重要,因此刚开始我会重复几次以防你忘记了:在 Python 里面所有东西都是对象。字符串是对象,列表是对象,函数是对象,类是对象,类的实例是对象,甚至模块也是对象。
Python 函数没有明确的开始(begin)或者结束(end),也没有用大括号来标记函数从哪里开始从哪里停止。唯一的定界符就是一个冒号(:)和代码自身的缩进。
def approximate_size(size, a_kilobyte_is_1024_bytes=True): ①
if size < 0: ②
raise ValueError('number must be non‐negative') ③
④
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in SUFFIXES[multiple]: ⑤
size /= multiple
if size < multiple:
return '{0:.1f} {1}'.format(size, suffix)
raise ValueError('number too large')
① 代码块是通过它们的缩进来定义的。我说的“代码块”,意思是指函数,if 语句、 for 循环、 while 循环,等等。缩进表示一个代码块的开始,非缩进表示一个代码的结束。没有明确的大括号、中括号、或者关键字。这意味着空白很重要,而且必须要是一致的。在这个例子中,这个函数按照四个空格缩进。它不需要一定是四个空格,只是需要保持一致。第一个没有缩进的行标记了这个函数的结束。
② 在 Python 中,一个if语句后面紧跟了一个代码块。如果if表达式的值为 true 则缩进的代码会被执行,否则它会跳到 else代码块(如果有的话)。注意表达式的周围没有括号。
③ 这一行在 if 代码块里面。这个 raise 语句将抛出一个异常(类型是 ValueError ),但只有在 size < 0 的时候才抛出。
④ 这不是函数的结尾。完全空白的行不算。它们使代码更加易读,但它们不算作代码块的定界符。这个函数在下一行继续。
⑤ 这个 for 循环也标记了一个代码块的开始。代码块可以包含多行,只要它们都按照同样的数额缩进。这个 for 循环里面有三行。对于多行的代码块,也没有其他特殊的语法,只要缩进就可以了。
在刚开始的一些反对声和一些类比到 Fortran 的嘲笑之后,你将会平和的看待这个并开始领会到它的好处。一个主要的好处是所有的 Python 程序看起来都类似,因为缩进是一个语言的要求,不是一个风格的问题。这使得阅读和理解其他人的 Python 代码更加容易。
Python 使用回车符来分割语句,使用一个冒号和缩进来分割代码块。 C++ 和 Java 使用分号来分割语句,使用大括号来分割代码块。
异常在 Python 中无处不在。事实上在标准 Python 库里面的每个模块都使用它们,而且在很多不同情形下, Python 自身也会抛出异常。贯穿这本书,你会反复的看到它们。
什么是一个异常?通常情况下,它是一个错误,提示某个东西出问题了。(不是所有的异常都是错误,但目前来说别担心那个) 某些程序语言鼓励对错误返回代码的使用,你可以对它进行检查。 Python 鼓励对异常的使用,你可以对它进行处理。
当一个错误发生在 Python Shell 里面的时候,它会打印一些关于这个异常以及它如何发生的详细信息,就此而已。这个被称之为一个 未被处理 的异常。在这个异常被抛出的时候,没有代码注意到并处理它,因此它把它的路径冒出来,返回到 Python Shell 的最顶层,输出一些调试信息,然后圆满结束。在这个 Shell 中,这没什么大不了的,但是如果在你的实际 Python 程序正在运行的时候发生,并且对这个异常没有做任何处理的话,整个程序就会嘎的一声停下来。可能那正是你想要的,也可能不是。
不像 Java, Python 函数不声明它们可能会抛出哪些异常。它取决于你去判断哪些可能的异常是你需要去捕获的。
一个异常不会造成整个程序崩溃。不过,异常是可以被处理的。有时候一个异常是真正地由于你代码里面的一个 bug 所引起的(比如访问一个不存在的变量),但有时候一个 异常是你可以预料到的东西。如果你在打开一个文件,它有可能不存在。如果你在导入一个模块,它可能没有被安装。如果你在连接到一个数据库,它有可能是无效的,或者你可能没有访问它需要的安全认证信息。如果你知道某行代码可能抛出一个异常,你应该使用 try…except 块来处理这个异常。
Python 使用 try…except 块来处理异常,使用raise 语句来抛出异常。 Java 和 C++ 使用try…catch 块来处理异常,使用 throw 语句来抛出异常。
这个 approximate_size() 函数在两个不同的情况下抛出异常:如果给定的 size 的值大于这个函数打算处理的值,或者如果它小于零。
if size < 0:
raise ValueError('number must be non‐negative')
抛出一个异常的语法足够简单。使用 raise 语句,紧跟着异常的名称,和一个人们可以读取的字符串用来调试。这个语法让人想起调用的函数。(实际上,异常是用类来实现的,这个 raise 语句事实上正在创建一个 ValueError 类的实例并传递一个字符串 ‘number must be non‐negative’ 到它的初始化方法里面。但是,我们已经有些超前了!)
你不需要在抛出异常的函数里面去处理它。如果一个函数没有处理它,这个异常会被传递到它的调用函数,然后那个函数的调用函数,等等“在这个堆栈上面。” 如果这个异常从来没有被处理,你的程序将会崩溃, Python 将会打印一个 “traceback” 的标准错误信息,并以此结束。这也可能正是你想要的,它取决于你的程序具体做什么。
其中一个 Python 的内置异常是ImportError,它会在你试图导入一个模块并且失败的时候抛出。这有可能由于多种原因引起,但是最简单的情况是当在你的 import 搜索路径里面找不到这个模块的时候会发生。你可以用这个来包含可选的特性到你的程序中。例如, 这个 chardet库提供字符编码自动检测。也许你的程序想在这个库存在的时候使用它,但是如果用户没有安装,也会优雅地继续执行。你可以使用 try…except 块来做这样的事情。
try:
import chardet
except ImportError:
chardet = None
然后,你可以用一个简单的 if 语句来检查 chardet 模块是否存在:
if chardet:
# do something
else:
# continue anyway
另一个对 ImportError 异常的通常使用是当两个模块实现了一个公共的 API,但我们更想要其中一个的时候。(可能它速度更快,或者使用了更少的内存。) 你可以试着导入其中一个模块,并且在这个模块导入失败的时候退回到另一个不同的模块。例如, XML 的章节谈论了两个模块实现一个公共的 API,叫做 ElementTree API。 第一个,lxml 是一个第三方的模块,你需要自己下载和安装。第二个, xml.etree.ElementTree 比较慢,但属于 Python 3 标准库的一部分。
try:
from lxml import etree
except ImportError:
import xml.etree.ElementTree as etree
在这个 try…except块的结尾,你导入了某个模块并取名为 etree。由于两个模块实现了一个公共的 API,你剩下的代码不需要一直去检查哪个模块被导入了。而且由于这个一定会被导入的模块总是叫做 etree,你余下的代码就不会被调用不同名称模块的 if 语句所打乱。
再看看 approximate_size() 函数里面的这行代码:
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
你从不声明这个 multiple 变量,你只是给它赋值了。这样就可以了,因为 Python 让你那样做。 Python 将不会让你做的是,引用了一个变量,但从不给它赋值。这样的尝试将会抛出一个 NameError 的异常。
>>> x
Traceback (most recent call last):
File "" , line 1, in <module>
NameError: name 'x' is not defined
>>> x = 1
>>> x
1
将来有一天,你会因为这个而感谢Python。
Python 里面所有的名称都是区分大小写的:变量名、函数名、类名、模块名称、异常名称。如果你可以获取它、设置它、调用它、构建它、导入它、或者抛出它,那么它就是区分大小写的。
>>> an_integer = 1
>>> an_integer
1
>>> AN_INTEGER
Traceback (most recent call last):
File "" , line 1, in <module>
NameError: name 'AN_INTEGER' is not defined
>>> An_Integer
Traceback (most recent call last):
File "" , line 1, in <module> NameError: name 'An_Integer' is not defined >>> an_inteGer
Traceback (most recent call last):
File "" , line 1, in <module> NameError: name 'an_inteGer' is not defined
Python 里面所有东西都是对象。
Python 模块是对象,并且有几个有用的属性。在你编写它们的时候,通过包含一个特殊的仅在你从命令行运行 Python 文件的时候执行的代码块,你可以使用这些属性容易地测试你的模块。看看 humansize.py 的最后几行代码:
if __name__ == '__main__':
print(approximate_size(1000000000000, False))
print(approximate_size(1000000000000))
像 C 语言一样, Python 使用 == 来做比较,用 =来赋值。不同于 C 语言的是, Python 不支持内嵌的赋值,所以没有机会出现你本以为在做比较而且意外的写成赋值的情况。
那么是什么使得这个 if 语句特别的呢?好吧,模块是对象,并且所有模块都有一个内置的属性 name(前后各有两个连续的下划线)。一个模块的 name 属性取决于你怎么来使用这个模块。如果你 import 这个模块,那么 name 就是这个模块的文件名,不包含目录的路径或者文件的扩展名。
>>> import humansize
>>> humansize.__name__
'humansize'
但是你也可以当作一个独立的程序直接运行这个模块,那样的话 name 将是一个特殊的默认值 main(前后各有两个连续的下划线)。 Python 将会评估这个 if 语句,寻找一个值为 true 的表达式,然后执行这个 if 代码块。在这个例子中,打印两个值。
c:\home\diveintopython3> c:\python31\python.exe
humansize.py
1.0 TB
931.3 GiB
这就是你的第一个 Python 程序!