Python 基础 -- Tutorial(一)

Python 3.9.16
python 官方文档

Python标准库

Python是一种易于学习、功能强大的编程语言。它具有高效的高级数据结构和简单但有效的面向对象编程方法Python优雅的语法和动态类型,以及它的解释性,使它成为大多数平台上许多领域的脚本和快速应用程序开发的理想语言。

Python解释器和广泛的标准库以源代码或二进制形式免费提供,适用于所有主要平台,可从Python网站https://www.python.org/获得,并且可以免费分发。同一站点还包含许多免费第三方Python模块、程序和工具的发行版和指针,以及其他文档。

Python解释器可以很容易地使用C或C++(或其他可从C调用的语言)实现的新函数和数据类型进行扩展。Python也适合作为可定制应用程序的扩展语言。

本教程非正式地向读者介绍Python语言和系统的基本概念和特性。有一个Python解释器可以帮助您进行实际操作,但所有示例都是独立的,因此本教程也可以离线阅读。

有关标准对象和模块的描述,请参见Python标准库Python语言参考给出了该语言更正式的定义。要用C或c++编写扩展,请阅读《扩展和嵌入Python解释器》和《Python/C API参考手册》。还有几本深入介绍Python的书。

本教程并不试图全面地涵盖每一个功能,甚至是每一个常用的功能。相反,它介绍了许多Python最值得注意的特性,并将使您对语言的风格和风格有一个很好的了解。阅读后,您将能够读取和编写Python模块和程序,并且您将准备好了解更多关于Python标准库中描述的各种Python库模块的信息。

术语表也值得一看。

1、开胃菜

如果你在电脑上做很多工作,最终你会发现有些任务你想要自动化。例如,您可能希望对大量文本文件执行搜索和替换,或者以复杂的方式重命名和重新排列一堆照片文件。也许你想写一个小的自定义数据库,或者一个专门的GUI应用程序,或者一个简单的游戏。

如果你是一个专业的软件开发人员,你可能不得不使用几个C/ c++ /Java库,但发现通常的编写/编译/测试/重新编译周期太慢了。也许您正在为这样的库编写测试套件,并发现编写测试代码是一项乏味的任务。或者,您可能已经编写了一个可以使用扩展语言的程序,并且您不想为您的应用程序设计和实现一种全新的语言。

Python就是适合你的语言。

您可以为其中一些任务编写Unix shell脚本或Windows批处理文件,但shell脚本最擅长移动文件和更改文本数据,不太适合GUI应用程序或游戏。您可以编写一个C/ c++ /Java程序,但即使是初稿也需要花费大量的开发时间。Python更易于使用,可在Windows、macOS和Unix操作系统上使用,并将帮助您更快地完成工作。

Python易于使用,但它是一种真正的编程语言,为大型程序提供了比shell脚本或批处理文件更多的结构和支持。另一方面,Python还提供了比C更多的错误检查,并且作为一种非常高级的语言,它具有内置的高级数据类型,例如灵活的数组和字典。由于其更通用的数据类型,Python适用于比Awk甚至Perl大得多的问题领域,但在Python中,许多事情至少和在这些语言中一样简单。

Python允许您将程序拆分为可在其他Python程序中重用的模块。它附带了大量的标准模块,您可以将其用作程序的基础-或者作为开始学习Python编程的示例。其中一些模块提供文件I/O、系统调用、套接字,甚至图形用户界面工具包(如Tk)的接口。

Python是一种解释性语言,它可以在程序开发期间节省大量时间,因为不需要编译和链接。解释器可以交互式地使用,这使得实验语言的特性、编写一次性程序或在自下而上的程序开发过程中测试功能变得容易。它也是一个方便的桌面计算器。

2、使用Python解释器

2.1. 调用解释器

Python解释器通常安装在/usr/local/bin/python3.9的位置;将/usr/local/bin放在Unix shell的搜索路径中,可以通过输入以下命令启动它:

python3.9

/usr/local/python是一个流行的替代位置

在主提示符处输入文件结束符(Unix上是Control-D, Windows上是Control-Z)会导致解释器以零退出状态退出。如果这不起作用,您可以通过输入以下命令退出解释器:quit()

在支持GNU Readline库的系统上,解释器的行编辑功能包括交互式编辑、历史替换和代码完成。也许查看是否支持命令行编辑的最快方法是在得到的第一个Python提示符中键入Control-P。如果它发出哔哔声,你可以进行命令行编辑;参见附录交互式输入编辑和历史替换键的介绍。如果没有出现任何东西,或者如果得到了^P响应,命令行编辑是不可用的;您只能使用backspace来从当前的行中删除字符。

解释器的操作有点像Unix shell:当使用连接到tty设备的标准输入调用时,它会交互式地读取和执行命令;当使用文件名参数调用或使用文件作为标准输入时,它将从该文件读取并执行脚本。

启动解释器的第二种方式是python -c command [arg] ...,它在命令中执行语句,类似于shell的-c选项。由于Python语句通常包含空格或其他shell特有的字符,因此通常建议将command用单引号括起来

一些Python模块也可用作脚本。可以使用python -m module [arg] ...,它执行module的源文件,就好像您在命令行上拼写出了它的全名一样。

当使用脚本文件时,有时能够运行脚本并在之后进入交互模式是很有用的。这可以通过在脚本之前传递-i来实现。

命令行和环境中描述了所有命令行选项。

2.1.1 参数传递

当解释器知道脚本名和其他参数后,脚本名和其他参数将被转换为字符串列表,并赋值给sys模块中的argv变量。您可以通过执行import sys来访问该列表。列表的长度至少为1;当没有给出脚本和参数时,sys.argv[0]是一个空字符串。当脚本名称为’-‘(表示标准输入)时,sys.argv[0]被设置为’-‘。使用-c命令时,sys.argv[0]被设置为’-c’。当使用-m模块时,sys.argv[0]设置为所定位模块的全称。在-c command或-m module之后找到的选项不会被Python解释器的选项处理所使用,而是留在要处理的命令或模块的sys.argv

2.1.2 交互模式

当从tty读取命令时,我们说解释器处于交互模式( interactive mode)。在这种模式下,它用主提示符(primary prompt)提示下一个命令,通常是三个大于号(>>>);对于连续行,它使用辅助提示符(secondary prompt)进行提示,默认为三个点()。在打印第一个提示符之前,解释器打印一条欢迎信息,说明它的版本号和版权声明:

ubuntu@VM-0-13-ubuntu:~$ python3
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

在进入多行结构时需要连续行。作为一个例子,看看这个if语句:

>>> the_world_is_flat = True
>>> if the_world_is_flat:
...     print("Be careful not to fall off!")
...
Be careful not to fall off!

2.2 解释器及其环境

2.2.1 源代码编码

默认情况下,Python源文件被视为UTF-8编码。在这种编码中,世界上大多数语言的字符都可以同时用于字符串字面量、标识符和注释中——尽管标准库只使用ASCII字符作为标识符,这是任何可移植代码都应该遵循的约定。要正确显示所有这些字符,编辑器必须识别文件是UTF-8格式,并且必须使用支持文件中所有字符的字体。

要声明非默认编码,应该在文件的第一行添加一个特殊的注释行。语法如下:

# -*- coding: encoding -*-

其中encoding是Python支持的有效编解码器(codecs)之一。

例如,要声明使用Windows-1252编码,源代码文件的第一行应该是:

# -*- coding: cp1252 -*-

第一行规则的一个例外是当源代码以UNIX“shebang”行开头时。在这种情况下,应该将编码声明添加到文件的第二行。例如:

#!/usr/bin/env python3
# -*- coding: cp1252 -*-

在Unix上,Python 3.x 解释器默认情况下,不会与名为python的可执行文件一起安装,因此它不会与同时安装的 python 2.x 可执行冲突。

3. Python的非正式介绍

在下面的例子中,输入和输出是通过提示符(>>>)的存在与否来区分的:为了重复这个例子,必须在提示符出现时输入提示符之后的所有内容;不以提示符开头的行是从解释器输出的。请注意,在示例中,行上的辅助提示符本身意味着您必须键入空行;用于结束多行命令。

您可以通过单击示例框右上角的>>>来切换提示和输出的显示。如果您隐藏了示例中的提示和输出,那么您可以轻松地将输入行复制并粘贴到解释器中。

本手册中的许多示例,甚至是在交互式提示符下输入的示例,都包含注释。Python中的注释以散列字符#开始,并延伸到物理行的末尾。注释可以出现在一行的开头,也可以出现在空格或代码的后面,但不能出现在字符串文本中。字符串字面值中的散列字符只是一个散列字符。由于注释是为了澄清代码而不是由Python解释的,因此在键入示例时可以省略注释。

一些例子:

# this is the first comment
spam = 1  # and this is the second comment
          # ... and now a third!
text = "# This is not a comment because it's inside quotes."

3.1 使用Python作为计算器

让我们尝试一些简单的Python命令。启动解释器并等待主提示符>>>。(应该不会花很长时间。)

3.1.1 数字

解释器就像一个简单的计算器:你可以在它里面输入一个表达式,它就会写出值。表达式语法很简单:运算符+-*/就像在大多数其他语言中一样(例如,Pascal或C);括号(())可用于分组。例如:

>>> 2 + 2
4
>>> 50 - 5*6
20
>>> (50 - 5*6) / 4
5.0
>>> 8 / 5  # division always returns a floating point number
1.6

整数(如2420)的类型是int,小数部分(如5.01.6)的类型是float。我们将在本教程后面看到更多关于数字类型的内容。

除法(/)总是返回浮点数。要进行向下取整 floor division 并获得整数结果(丢弃任何小数结果),您可以使用//运算符;要计算余数,可以使用%:

>>> 17 / 3  # classic division returns a float
5.666666666666667
>>>
>>> 17 // 3  # floor division discards the fractional part
5
>>> 17 % 3  # the % operator returns the remainder of the division
2
>>> 5 * 3 + 2  # floored quotient * divisor + remainder
17

在Python中,可以使用**运算符来计算幂:

>>> 5 ** 2  # 5 squared
25
>>> 2 ** 7  # 2 to the power of 7
128

由于**的优先级高于--3**2将被解释为-(3**2),因此结果为-9。为了避免这种情况并得到9,您可以使用(-3)**2

等号(=)用于为变量赋值。之后,在下一个交互提示符之前没有显示任何结果:

>>> width = 20
>>> height = 5 * 9
>>> width * height
900

如果一个变量没有被“定义”(赋值),尝试使用它会给你一个错误:

>>> n  # try to access an undefined variable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'n' is not defined

它完全支持浮点数;带有混合类型操作数的操作符将整型操作数转换为浮点数:

>>> 4 * 3.75 - 1
14.0

在交互模式下,最后打印的表达式被赋值给变量_。这意味着当你使用Python作为桌面计算器时,继续计算会更容易一些,例如:

>>> tax = 12.5 / 100
>>> price = 100.50
>>> price * tax
12.5625
>>> price + _
113.0625
>>> round(_, 2)
113.06

这个变量应该被用户视为只读的。不要显式地给它赋值——您将创建一个同名的独立局部变量,用它的神奇行为掩盖内置变量。

除了intfloat, Python还支持其他类型的数字,如DecimalFraction。Python还内置了对复数的支持,并使用jJ后缀来表示虚部(例如3+5j)

3.1.2 字符串

除了数字,Python还可以操作字符串,字符串可以用几种方式表示。它们可以用单引号('…')或双引号("…")括起来,结果相同2。\可以用来转义引号:

>>> 'spam eggs'  # single quotes
'spam eggs'
>>> 'doesn\'t'  # use \' to escape the single quote...
"doesn't"
>>> "doesn't"  # ...or use double quotes instead
"doesn't"
>>> '"Yes," they said.'
'"Yes," they said.'
>>> "\"Yes,\" they said."
'"Yes," they said.'
>>> '"Isn\'t," they said.'
'"Isn\'t," they said.'

在交互式解释器中,输出字符串用引号括起来,特殊字符用反斜杠转义。虽然这有时看起来与输入不同(封闭引号可能会改变),但这两个字符串是等效的。如果字符串包含单引号而不包含双引号,则将字符串括在双引号中,否则将字符串括在单引号中。print()函数通过省略引号和打印转义字符和特殊字符来产生更可读的输出:

>>> '"Isn\'t," they said.'
'"Isn\'t," they said.'
>>> print('"Isn\'t," they said.')
"Isn't," they said.
>>> s = 'First line.\nSecond line.'  # \n means newline
>>> s  # without print(), \n is included in the output
'First line.\nSecond line.'
>>> print(s)  # with print(), \n produces a new line
First line.
Second line.

如果你不想让以\开头的字符被解释为特殊字符,你可以使用原始字符串,在第一个引号前添加一个r:

>>> print('C:\some\name')  # here \n means newline!
C:\some
ame
>>> print(r'C:\some\name')  # note the r before the quote
C:\some\name

字符串字面值可以跨越多行。一种方法是使用三重引号:"""..."""或者'''...'''。字符串中会自动包含行尾,但可以通过在行尾添加\来防止这种情况。下面的例子:

print("""\
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to
""")

产生以下输出(注意不包括初始换行符):

Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to

字符串可以用+操作符连接(粘合在一起),并用*重复:

>>> # 3 times 'un', followed by 'ium'
>>> 3 * 'un' + 'ium'
'unununium'

相邻的两个或多个字符串字面值(string literals,即括在引号之间的字面值)会自动连接起来。

>>> 'Py' 'thon'
'Python'

当你想要中断长字符串时,这个功能特别有用:

>>> text = ('Put several strings within parentheses '
...         'to have them joined together.')
>>> text
'Put several strings within parentheses to have them joined together.'

这只适用于两个字面量,而不适用于变量或表达式:

>>> prefix = 'Py'
>>> prefix 'thon'  # can't concatenate a variable and a string literal
  File "<stdin>", line 1
    prefix 'thon'
                ^
SyntaxError: invalid syntax
>>> ('un' * 3) 'ium'
  File "<stdin>", line 1
    ('un' * 3) 'ium'
                   ^
SyntaxError: invalid syntax

如果你想连接变量或者一个变量和一个字面值,使用+:

>>> prefix + 'thon'
'Python'

字符串可以被索引(indexed, 下标),第一个字符的索引为0没有单独的字符类型;字符就是一个大小为1的字符串:

>>> word = 'Python'
>>> word[0]  # character in position 0
'P'
>>> word[5]  # character in position 5
'n'

索引也可以是负数,从右边开始计数:

>>> word[-1]  # last character
'n'
>>> word[-2]  # second-last character
'o'
>>> word[-6]
'P'

注意,因为-0和0是一样的,所以负号从-1开始。

除了索引之外,还支持切片(slicing)。索引用于获取单个字符,但切片允许您获取子字符串:

>>> word[0:2]  # characters from position 0 (included) to 2 (excluded)
'Py'
>>> word[2:5]  # characters from position 2 (included) to 5 (excluded)
'tho'

切片索引有有用的默认值;省略的第一个索引默认为零,省略的第二个索引默认为被切片字符串的大小。

>>> word[:2]   # character from the beginning to position 2 (excluded)
'Py'
>>> word[4:]   # characters from position 4 (included) to the end
'on'
>>> word[-2:]  # characters from the second-last (included) to the end
'on'

请注意,开头总是包括在内,结尾总是不包括在内。这确保了 s[:i] + s[i:]总是等于s:

>>> word[:2] + word[2:]
'Python'
>>> word[:4] + word[4:]
'Python'

记住切片如何工作的一种方法是将索引视为指向字符之间的索引,第一个字符的左边缘编号为0。那么,一个包含n个字符的字符串的最后一个字符的右边缘的索引为n,例如:

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

第一行数字给出了字符串中索引0…6的位置;第二行给出相应的负指数。从ij的切片由分别标记为ij的边(edges)之间的所有字符组成。

对于非负指标,如果两个指标都在限定范围内,则切片的长度为指标之差。例如,word[1:3]的长度是2。

尝试使用太大的索引将导致错误:

>>> word[42]  # the word only has 6 characters
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range

然而,超出范围的切片索引在用于切片时被优雅地处理:

>>> word[4:42]
'on'
>>> word[42:]
''

Python字符串不能被改变——它们是不可变的。因此,对字符串中的索引位置赋值会导致错误:

>>> word[0] = 'J'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> word[2:] = 'py'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

如果你需要一个不同的字符串,你应该创建一个新的:

>>> 'J' + word[1:]
'Jython'
>>> word[:2] + 'py'
'Pypy'

内置函数len()返回字符串的长度:

>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34

Text Sequence Type — str
字符串是序列类型(sequence types)的示例,并支持这些类型支持的常见操作。
String Methods
字符串支持大量用于基本转换和搜索的方法。
Formatted string literals
具有嵌入表达式的字符串字面值。
Format String Syntax
使用str.format()格式化字符串的信息。
printf-style String Formatting
这里更详细地描述了当字符串是%操作符的左操作数时调用的旧格式化操作。

3.1.3 列表

Python知道许多复合数据类型,用于将其他值组合在一起。最通用的是列表,它可以写成方括号之间的逗号分隔值(项)的列表(list)。列表可能包含不同类型的项,但通常这些项都具有相同的类型

>>> squares = [1, 4, 9, 16, 25]
>>> squares
[1, 4, 9, 16, 25]

像字符串(strings, 以及所有其他内置序列类型)一样,列表可以被索引和切片:

>>> squares[0]  # indexing returns the item
1
>>> squares[-1]
25
>>> squares[-3:]  # slicing returns a new list
[9, 16, 25]

所有切片操作返回一个包含所请求元素的新列表。这意味着下面的切片返回列表的浅拷贝:

>>> squares[:]
[1, 4, 9, 16, 25]

列表还支持连接这样的操作:

>>> squares + [36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

不可变的字符串不同,列表是一种可变类型,也就是说,可以改变它们的内容:

>>> cubes = [1, 8, 27, 65, 125]  # something's wrong here
>>> 4 ** 3  # the cube of 4 is 64, not 65!
64
>>> cubes[3] = 64  # replace the wrong value
>>> cubes
[1, 8, 27, 64, 125]

你也可以通过使用append()方法在列表的末尾添加新项(我们将在后面看到更多关于方法的内容):

>>> cubes.append(216)  # add the cube of 6
>>> cubes.append(7 ** 3)  # and the cube of 7
>>> cubes
[1, 8, 27, 64, 125, 216, 343]

给切片赋值也是可能的,这甚至可以改变列表的大小或完全清除它:

>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> letters
['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> # replace some values
>>> letters[2:5] = ['C', 'D', 'E']
>>> letters
['a', 'b', 'C', 'D', 'E', 'f', 'g']
>>> # now remove them
>>> letters[2:5] = []
>>> letters
['a', 'b', 'f', 'g']
>>> # clear the list by replacing all the elements with an empty list
>>> letters[:] = []
>>> letters
[]

内置函数len()也适用于列表:

>>> letters = ['a', 'b', 'c', 'd']
>>> len(letters)
4

可以嵌套列表(创建包含其他列表的列表),例如:

>>> a = ['a', 'b', 'c']
>>> n = [1, 2, 3]
>>> x = [a, n]
>>> x
[['a', 'b', 'c'], [1, 2, 3]]
>>> x[0]
['a', 'b', 'c']
>>> x[0][1]
'b'

3.2 编程的第一步

当然,我们可以使用Python完成比二加二更复杂的任务。例如,我们可以这样写斐波那契数列的初始子序列:

>>> # Fibonacci series:
... # the sum of two elements defines the next
... a, b = 0, 1
>>> while a < 10:
...     print(a)
...     a, b = b, a+b
...
0
1
1
2
3
5
8

这个例子引入了几个新特性。

  • 第一行包含多个分配(multiple assignment):变量ab同时获得新的值01。在最后一行中再次使用,证明右边的表达式首先在任何作业发生前进行评估。右边的表达式从左向右进行评估
  • 只要条件(这里:a < 10)保持为真,while循环就会执行。在Python中,像在C中一样,任何非零整数值都是true;0为假。条件也可以是字符串或列表值,实际上是任何序列;任何非零长度的序列都为真,空序列为假。示例中使用的测试是一个简单的比较。标准比较操作符的编写与C语言相同:<(小于)、>(大于)、==(等于)、<=(小于或等于)、>=(大于或等于)和!=(不等于)。
  • 循环体是缩进的:缩进是Python对语句进行分组的方式。在交互式提示符中,您必须为每个缩进行键入一个制表符或空格。在实践中,您将使用文本编辑器为Python准备更复杂的输入;所有体面的文本编辑器都有自动缩进功能。当一个复合语句被插入时,它必须遵循一个空白行来表示完成(因为解析器不能在您输入最后一行时无法估计)。注意,在一个基本块内的每一条线都必须按相同的数量缩进
  • 函数print()写入参数(s)的值。它不同于写你想写的表达式(正如我们之前在计算器示例中所做的那样),它可以处理多个参数、浮点量和字符串的方式中。字符串在没有引号的情况下打印,在项目之间插入一个空间,这样你就可以很好地格式化东西,比如这样:
>>> i = 256*256
>>> print('The value of i is', i)
The value of i is 65536

print() 的关键字参数end可用于避免输出后的换行符,或以不同的字符串结束输出:

>>> a, b = 0, 1
>>> while a < 1000:
...     print(a, end=',')
...     a, b = b, a+b
...
0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,

4、流程控制

除了刚刚介绍的while语句之外,Python还使用了其他语言中常见的流控制语句,只是有一些改动。

4.1 if 语句

也许最著名的语句类型是if语句。例如:

x = int(input("Please enter an integer: "))

if x < 0:
    x = 0
    print('Negative changed to zero')
elif x == 0:
    print('Zero')
elif x == 1:
    print('Single')
else:
    print('More')

可以有零个或多个elif部分,else部分是可选的。关键字’elif '是’else if '的缩写,用于避免过度缩进。if … elif … elif …序列是其他语言中switchcase语句的替代品。

4.2 for 语句

Python中的for语句与您可能在C或Pascal中使用的语句略有不同。Python的for语句不是总是在数字的等差数列上迭代(如Pascal),也不是让用户能够定义迭代步骤和停止条件(如C语言),Python的for语句迭代任何序列(列表或字符串)中的项,按照它们在序列中出现的顺序。例如(没有双关的意思):

# Measure some strings:
words = ['cat', 'window', 'defenestrate']
for w in words:
    print(w, len(w))

在迭代同一集合的同时修改该集合的代码可能很难得到正确的处理。相反,通常更直接的方法是遍历集合的副本或创建一个新集合:

# Strategy:  Iterate over a copy
for user, status in users.copy().items():
    if status == 'inactive':
        del users[user]

# Strategy:  Create a new collection
active_users = {
    
    }
for user, status in users.items():
    if status == 'active':
        active_users[user] = status

4.3 range()函数

如果确实需要迭代一组数字,内置函数range()会派上用场。它生成等差数列:

for i in range(5):
    print(i)

给定的终点从来不是生成序列的一部分;range(10)生成10个值,这是长度为10的序列项的合法索引。可以让range 从另一个数字开始,或者指定一个不同的增量(甚至是负的;有时这被称为“步长,step”):

list(range(5, 10))


list(range(0, 10, 3))


list(range(-10, -100, -30))

为了迭代序列的索引,你可以组合range()len(),如下所示:

a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
    print(i, a[i])

然而,在大多数情况下,使用enumerate()函数是很方便的,请参阅循环技术

    a = ['Mary', 'had', 'a', 'litle', 'lamb']
    for i, j in enumerate(a):
        print(i, j)

如果你只是打印一个范围,会发生一件奇怪的事情:

print(range(10))
// range(0, 10)

在许多方面,range()返回的对象表现得好像它是一个列表,但实际上它不是。它是一个对象,当您迭代它时,它返回所需序列的连续项,但它并不真正创建列表,因此节省了空间。

我们说这样的对象是可迭代的也就是说,适合作为函数和构造的目标,这些函数和构造期望从中获得连续的项,直到耗尽为止。我们已经看到for语句就是这样一个结构,而一个接受可迭代对象的函数的例子是sum():

sum(range(4))  # 0 + 1 + 2 + 3

稍后我们将看到更多返回可迭代对象并接受可迭代对象作为参数的函数。在数据结构一章中,我们将更详细地讨论list()

4.4 循环中的break和continue语句,以及else子句

break语句,像C中一样,从最内层的forwhile循环中跳出来。

循环语句可以有else子句;当循环因可迭代对象耗尽而终止(使用for)或当条件变为false(使用while)时执行,但当循环被break语句终止时不执行。下面的循环就是一个例子,它搜索素数:

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            print(n, 'equals', x, '*', n//x)
            break
    else:
        # loop fell through without finding a factor
        print(n, 'is a prime number')

2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(是的,这是正确的代码。仔细看:else子句属于for循环,而不是if语句。)

if语句相比,与循环一起使用时,else子句与try语句的else子句有更多的共同之处:try语句的else子句在没有异常发生时运行,而循环的else子句在没有break发生时运行。有关try语句和异常的更多信息,请参见处理异常

continue语句也从C语言中借用,继续循环的下一次迭代:

for num in range(2, 10):
    if num % 2 == 0:
        print("Found an even number", num)
        continue
    print("Found an odd number", num)

4.5 pass 语句

pass语句什么也不做。当在语法上需要一条语句,但程序不需要任何操作时,可以使用它。例如:

while True:
    pass  # Busy-wait for keyboard interrupt (Ctrl+C)

这通常用于创建最小类:

class MyEmptyClass:
    pass

另一个可以使用pass的地方是在编写新代码时作为函数或条件体的占位符,允许您在更抽象的级别上进行思考。pass被静默忽略:

def initlog(*args):
    pass   # Remember to implement this!

4.6. 定义函数

我们可以创建一个函数,将斐波那契数列写到任意边界:

def fib(n):    # write Fibonacci series up to n
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

# Now call the function we just defined:
fib(2000)
# 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

关键字def引入了一个函数定义( function definition)。它必须后跟函数名和带圆括号的形式参数列表。构成函数体的语句从下一行开始,必须缩进。

函数体的第一个语句可以是字符串字面值(string literal);这个字符串字面值是函数的文档字符串,或docstring。(更多关于文档字符串的信息可以在文档字符串部分找到。)有些工具使用文档字符串来自动生成在线或打印文档,或者让用户交互式地浏览代码;在您编写的代码中包含文档字符串是一种很好的做法,因此请养成这种习惯。

函数的执行(execution)引入了一个新的符号表,用于函数的局部变量。更准确地说,函数中的所有变量赋值(variable assignments)都将值存储在局部符号表中;而变量引用(variable references)首先在局部符号表中查找,然后在外围函数(enclosing function)的局部符号表中查找,然后在全局符号表中查找,最后在内置名称(built-in names)表中查找。因此,全局变量和外围函数的变量不能在函数内直接赋值(除非全局变量在global语句中命名,或者外围函数的变量在nonlocal中命名),尽管它们可以被引用。

函数调用的实际形参(实参, arguments)在调用时被引入到被调用函数的局部符号表中;因此,使用按值调用(call by value)传递参数(其中值始终是对象引用,而不是对象的值)。当一个函数调用另一个函数,或者递归地调用自己时,一个新的局部符号表将被创建。

函数定义将函数名与当前符号表中的函数对象关联起来。解释器将该名称所指向的对象识别为用户定义的函数。其他名称也可以指向同一个函数对象,也可以用来访问该函数:

fib

f = fib
f(100)

在其他语言中,你可能会反对fib不是一个函数,而是一个过程,因为它不返回值。事实上,即使没有return语句的函数也会返回一个值,尽管是一个相当无聊的值。这个值被称为None(它是一个内置名称)。如果值None是唯一写入的值,则解释器通常会抑制该值的写入。如果你真的想使用print(),你可以看到它:

fib(0)
print(fib(0))

编写一个函数返回斐波那契数列的数字列表,而不是打印出来,这很简单:

def fib2(n):  # return Fibonacci series up to n
    """Return a list containing the Fibonacci series up to n."""
    result = []
    a, b = 0, 1
    while a < n:
        result.append(a)    # see below
        a, b = b, a+b
    return result

f100 = fib2(100)    # call it
f100                # write the result

像往常一样,这个例子演示了一些新的Python特性:

  • return语句从一个函数返回一个值。不带表达式参数的return返回None。执行到函数的末尾也会返回None
  • 语句result.append(a)调用list 对象result的一个方法。方法是一个“属于”对象的函数,命名为obj.methodname,其中obj是某个对象(可能是一个表达式),methodname是由对象类型定义的方法名。不同的类型定义不同的方法。不同类型的方法可以有相同的名称而不会产生歧义。(可以使用类定义自己的对象类型和方法,请参阅)示例中显示的方法append()是为列表对象定义的;它在列表的末尾添加一个新元素。在本例中,它相当于result = result + [a],但效率更高。

4.7 更多关于定义函数的内容

也可以定义带有可变数量参数的函数有三种形式,可以组合使用

4.7.1 默认参数值

最有用的形式是为一个或多个参数指定默认值。这将创建一个函数,该函数可以使用比定义允许的更少的参数来调用。例如:

def ask_ok(prompt, retries=4, reminder='Please try again!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'):
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise ValueError('invalid user response')
        print(reminder)

这个函数可以通过几种方式调用:

  • 只给出强制参数:ask_ok('Do you really want to quit?')
  • 给出一个可选参数:ask_ok('OK to overwrite the file?', 2)
  • 甚至给出所有的参数:ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

这个例子还介绍了in关键字。这将测试序列是否包含某个值。

默认值在定义作用域(defining scope)内的函数定义点计算,因此

i = 5

def f(arg=i):
    print(arg)

i = 6
f()
# 将打印5。

重要警告: 默认值只计算一次。当默认值是可变对象(如列表、字典或大多数类的实例)时,这一点会有所不同。例如,下面的函数会在后续调用中累积传递给它的参数:

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

print(f(1))
print(f(2))
print(f(3))

这将打印:

[1]
[1, 2]
[1, 2, 3]

如果你不想在后续调用之间共享默认值,你可以这样写函数:

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

4.7.2 关键字参数

也可以使用 kwarg=value形式的关键字参数来调用函数。例如,下面的函数:

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")

接受一个必需参数(voltage)和三个可选参数(state, action, and type)。这个函数可以通过以下任何一种方式调用:

parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

但是下面所有的调用都是无效的:

parrot()                     # required argument missing
parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument 
							 # 关键字参数后的非关键字参数

parrot(110, voltage=220)     # duplicate value for the same argument
parrot(actor='John Cleese')  # unknown keyword argument

在函数调用中,关键字参数必须紧跟位置参数传递的所有关键字参数必须与函数接受的参数之一匹配(例如actor不是parrot函数的有效参数),它们的顺序并不重要这也包括非可选参数(例如parrot(voltage=1000)也是有效的)。任何参数都不能接受一个值超过一次。下面是一个由于这个限制而失败的例子:

def function(a):
    pass

function(0, a=0)

###########
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'

当最后一个形式为**name的形参出现时,它会接收一个字典(参见映射类型- dict),其中包含除了与形参对应的那些外的所有关键字参数。这可以与*name形式的形式参数(在下一小节中描述)结合使用,该形式参数接收包含形式参数列表之外的位置参数元组。(*name必须出现在**name之前。)例如,如果我们这样定义一个函数:

def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    for kw in keywords:
        print(kw, ":", keywords[kw])

它可以这样被调用:

cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")

当然,它会打印:

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch

请注意,打印关键字参数的顺序保证与在函数调用中提供它们的顺序相匹配。

4.7.3 特殊参数

默认情况下,参数可以通过位置或显式通过关键字传递给Python函数。为了可读性和性能,限制参数的传递方式是有意义的,这样开发人员只需要查看函数定义就可以确定项是按位置、按位置或关键字还是按关键字传递。

函数定义可能是这样的:

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

其中/*是可选的。如果使用,这些符号通过实参传递给函数的方式来指示形参的类型:仅限位置型、仅限位置或关键字型和仅限关键字型。关键字参数也称为命名参数

4.7.3.1 Positional-or-Keyword参数

如果/*没有出现在函数定义中,参数可以通过位置或关键字传递给函数。

4.7.3.2 Positional-Only参数

更详细地看一下,可以将某些参数标记为仅限位置传参的。如果是位置型,则参数的顺序很重要,不能通过关键字传递参数。仅限位置的参数放在/(正斜杠)之前。/用于从逻辑上将仅位置参数与其他参数分开。如果函数定义中没有/,则没有位置参数。

/后面的参数可以是position -or-keywordkeyword-only

4.7.3.3 Keyword-Only参数

要将参数标记为仅限关键字传参,表明参数必须通过关键字参数传递,请在参数列表中第一个仅限关键字参数之前放置*

4.7.3.4 函数的例子

考虑下面的函数定义示例,密切关注/*标记:

def standard_arg(arg):
    print(arg)

def pos_only_arg(arg, /):
    print(arg)

def kwd_only_arg(*, arg):
    print(arg)

def combined_example(pos_only, /, standard, *, kwd_only):
    print(pos_only, standard, kwd_only)

第一个函数定义,standard_arg,是最熟悉的形式,对调用约定没有限制,参数可以通过位置或关键字传递:

standard_arg(2)

standard_arg(arg=2)

第二个函数pos_only_arg被限制为只使用位置形参,因为函数定义中有一个/:

pos_only_arg(1)


pos_only_arg(arg=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

第三个函数kwd_only_args只允许在函数定义中用*表示的关键字参数:

kwd_only_arg(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

kwd_only_arg(arg=3)

最后一个在同一个函数定义中使用了所有三种调用约定:

>>> combined_example(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3

>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

最后,考虑这个函数定义,它在位置参数name和有name为键的**kwds之间存在潜在的冲突:

def foo(name, **kwds):
    return 'name' in kwds

没有可能的调用会使它返回True,因为关键字’name’总是绑定到第一个参数。例如:

>>> foo(1, **{
    
    'name': 2})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>

但是使用/(位置参数),这是可能的,因为它允许name作为位置参数,'name'作为关键字参数的键:

def foo(name, /, **kwds):
    return 'name' in kwds
>>> foo(1, **{
    
    'name': 2})
True

换句话说,仅位置参数的名称可以在**kwds中使用而不会产生歧义。

4.7.4 任意参数列表

最后,最不常用的选项是指定可以使用任意数量的参数调用函数。这些参数将被封装在一个元组中(参见元组和序列)。在可变数量的参数之前,可能出现零个或多个正常参数。

def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))

通常,这些可变参数将排在形式参数列表的最后,因为它们包含传递给函数的所有剩余输入参数出现在*args参数之后的任何形式参数都是“仅关键字”参数,这意味着它们只能用作关键字而不是位置参数

>>> def concat(*args, sep="/"):
...     return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

4.7.5 解包(Unpacking)参数列表

当参数已经在列表或元组中,但需要为需要单独位置参数的函数调用解压缩时,则会出现相反的情况。例如,内置的range()函数需要单独的startstop参数。如果它们不能单独使用,使用*-操作符编写函数调用,从列表或元组中解包参数:

>>> list(range(3, 6))            # normal call with separate arguments
[3, 4, 5]
>>> args = [3, 6]
>>> list(range(*args))            # call with arguments unpacked from a list
[3, 4, 5]

以同样的方式,字典可以使用**-操作符传递关键字参数:

def parrot(voltage, state='a stiff', action='voom'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.", end=' ')
    print("E's", state, "!")

d = {
    
    "voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)

4.7.6 Lambda表达式

可以使用lambda关键字创建小型匿名函数。这个函数返回两个参数的和:lambda a, b: a+bLambda函数可以在任何需要函数对象的地方使用它们在语法上被限制为单个表达式。从语义上讲,它们只是普通函数定义的语法糖像嵌套函数定义一样,lambda函数可以从包含范围引用变量:

>>> def make_incrementor(n):
...     return lambda x: x + n
...
>>> f = make_incrementor(42)
>>> f(0)
42
>>> f(1)
43

上面的例子使用lambda表达式返回一个函数。另一种用法是传递一个小函数作为参数:

>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

4.7.7 文档字符串

以下是关于文档字符串的内容和格式的一些约定。

第一行应该是对对象用途的简短总结。为简洁起见,它不应该显式地声明对象的名称或类型,因为这些可以通过其他方式获得(除非名称恰好是描述函数操作的动词)。这一行应该以大写字母开头,以句号结束。

如果文档字符串中有更多行,第二行应该为空,在视觉上将摘要与描述的其余部分分开。接下来的几行应该是一个或多个描述对象调用约定、其副作用等的段落。

Python解析器不会从Python中的多行字符串字面量中删除缩进,因此处理文档的工具必须在需要时删除缩进。这是使用以下约定完成的。字符串第一行之后的第一个非空行决定了整个文档字符串的缩进量。(我们不能使用第一行,因为它通常与字符串的开始引号相邻,所以它的缩进在字符串字面量中不明显。)然后从字符串的所有行开始删除与此缩进“等效”的空白。不应该出现缩进较少的行,但如果它们出现,则应删除其所有前导空白。应该在扩展制表符(通常为8个空格)之后测试空白的等价性。

下面是一个多行文档字符串的例子:

def my_function():
    """Do nothing, but document it.

    No, really, it doesn't do anything.
    """
    pass

print(my_function.__doc__)
Do nothing, but document it.

    No, really, it doesn't do anything.

4.7.8 函数注解

函数注解(Function Annotations)是关于用户定义函数使用的类型的完全可选的元数据信息(更多信息请参见PEP 3107和PEP 484)。

注解作为字典存储在函数的__annotations__属性中,对函数的任何其他部分都没有影响。参数注解是由参数名后面的冒号定义的,后面是一个求值为注释值的表达式返回注释由形参列表和表示def语句结束的冒号之间的字面值->和后跟表达式定义。下面的例子有一个必选参数,一个可选参数,返回值带注释:

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'

4.8 间奏曲:编码风格

既然您即将编写更长、更复杂的Python代码,那么现在是讨论编码风格的好时机了。大多数语言都可以用不同的风格编写(或者更简洁地说,格式化);有些比其他的更易读。让别人容易阅读你的代码总是一个好主意,采用一个好的编码风格对这一点有很大的帮助。

对于Python, PEP 8已经成为大多数项目遵循的风格指南;它促进了一种非常易读和赏心悦目的编码风格。每个Python开发人员都应该在某个时候阅读它;以下是为您摘录的最重要的几点:

  • 使用4个空格的缩进,不要制表符。
    4个空格是小缩进(允许更大的嵌套深度)和大缩进(更容易阅读)之间的一个很好的折衷。制表符会引起混淆,最好不要使用。
  • 换行,使它们不超过79个字符。
    这有助于使用小型显示器的用户,并使得在较大的显示器上并排放置多个代码文件成为可能。
  • 使用空行分隔函数和类,以及函数内部较大的代码块。
  • 如果可能的话,把注释单独放在一行
  • 使用文档字符串
  • 在运算符周围和逗号之后使用空格,但不要直接在括号结构中使用空格:a = f(1, 2) + g(3, 4)
  • 一致地命名你的类和函数;惯例是对类使用UpperCamelCase ,对函数和方法使用lowercase_with_underscores 。始终使用self作为第一个方法参数的名称(有关类和方法的更多信息,请参阅A first Look at Classes)。
  • 如果您的代码打算在国际环境中使用,则不要使用花哨的编码。Python的默认值,UTF-8,甚至纯ASCII在任何情况下都是最好的。
  • 同样,如果使用不同语言的人阅读或维护代码的可能性很小,就不要在标识符中使用非ascii字符

猜你喜欢

转载自blog.csdn.net/chinusyan/article/details/131112303