https://docs.python.org/3/tutorial/index.html
尝试读读一手资料,下文主要是翻译 + 摘抄——确实学到很多
§1~§4 主要涉及解释器、基本语法、基本数据类型。

Python 很简单,很强大。高级数据结构,高效的面向对象编程,优雅的语法 elegant syntax,动态类型 dynamic typing,解释性本质 interpreted nature。

Python 的解释器 interpreter 和标准库都能以源代码或二进制形式 source or binary form 获取,还有很多第三方库。

Python 的解释器可以使用 C(或者任何 C 调用的语言 other languages callable from C) 编写的函数和数据结构轻松地扩展。

本教程介绍 Python 最引人注目 noteworthy 的特性,展现这种语言的 flavor and style。


1. Whetting Your Appetite

https://docs.python.org/3/tutorial/appetite.html

开胃菜

为什么用 Python?想做一些自动化操作,There’s some task you’d like to automate. 一个专业的软件开发流程,write/compile/test/re-compile cycle,可能感到冗长乏味 tedious。

Unix shell script 脚本适合移动文件、操作数据,不适合 GUI 或游戏;C/C++/Java 开发时间长。Python 更好。

Python 比脚本语言提供更多结构,比 C 提供更多错误检查。作为高等级语言,具有高级数据结构,比如灵活的 array 和 dictionary。

Python 允许把程序分成模块进行复用,附带大量的标准模块。

Python 是一种解释性语言,因此不需要编译 compilation 和链接 linking。

Python 程序紧凑、可读性强,因为:高级数据结构,在单句中实现复杂的操作;语句分组是通过缩进 indentation 实现的,而不是大括号;不需要变量声明。

Python 是可扩展的,用 C 编写的模块可以链接进去。

Python 的名字来源于 BBC 节目《Monty Python’s Flying Circus》飞行马戏团,和爬行动物无关。

下一章解释使用解释器的原理。其余部分通过示例介绍各种功能,从简单表达式、数据结构到异常、自定义的类等概念。


2. Using the Python Interpreter

https://docs.python.org/3/tutorial/interpreter.html

2.1. Invoking the Interpreter

Python 解释器 interpreter 安装在 /usr/local/bin/python3.9,把/usr/local/bin放在搜索路径下,就能在命令行调出 Python。

在主提示符 prompt 后输入一个文件结尾字符 (Unix 是 Ctrl-D,Win 是 Ctrl-Z),可以让解释器以零状态退出。也可以用 quit() 命令。

在支持 GNU Readline 库的系统上,解释器的行编辑特性包括交互式编辑、历史替换和代码补全。(就是方向键可以查看历史命令、Tab 可以自动补全的功能)Bash 行操作clink: 让 Windows Cmd 拥有 readline 能力

解释器的操作有点像 Unix shell,当使用 tty 设备(就是串口设备,显示器键盘等)时,它读取命令并交互式执行;当以文件作为标准输入调用时,它将读取并执行文件中的脚本。

启动解释器的另一种方式是python -c command [arg] ...,建议用单引号把命令括起来。

其它命令行选项看这里 Command line and environment.

2.1.1 Argument Passing

解释器得到命令后,脚本名和参数会转换成字符串列表,分配给 sys 模块的 argv 变量。除了-c、-m,其他参数传递给模块使用。

argc 是 argument count 的缩写,argv 是 argument vector 的缩写。

2.1.2. Interactive Mode

交互模式下有主提示符 (primary prompt)>>>,对于多行命令有次提示符 (secondary prompt)...

if或者def的时候会自动变成次提示符的。

2.2. The Interpreter and Its Environment

2.2.1. Source Code Encoding

默认情况下,Python 源文件被视为采用 UTF-8 编码。

ASCII 包含英文、数字、符号,1 个字节;Unicode 包含多国语言,通常 2 个字节,内存中一般用这个处理;UTF-8 变长编码,用于传输。字符串和编码 廖雪峰的官方网站

要声明默认编码以外的其他编码,应在文件的第一行添加一个特殊的注释行。 语法如下:# -*- coding: encoding -*-

一个例外就是为了告诉 Linux/OS X 系统这是一个 Python 可执行程序 (Windows 系统会忽略这个注释),第一行应该写#!/usr/bin/env python3


3. An Informal Introduction to Python

https://docs.python.org/3/tutorial/introduction.html

非正式介绍

Python 中的注释以井号字符#开头,并延伸到物理行的末尾。

3.1. Using Python as a Calculator

3.1.1. Numbers

整型 int,浮点数 float。同时使用会自动转换成 float。

除法/始终返回浮点数,//可以向下取整 (discards the fractional part),%计算余数 (returns the remainder of the division)。

结果 * 除数 + 余数 (result * divisor + remainder)

**计算幂,如2 ** 7表示 2 的 7 次方 (2 to the power of 7)。

等号=用来把数值分配 (assign) 给变量。

在交互模式下,最后一个打印的表达式会赋值给变量_,就像是计算器里的"Ans"。要注意的是这个变量应该被用户视为只读的,不要显式地给它赋值 (explicitly assign a value to it),你会创建一个同名的独立局部变量,用它的神奇行为掩盖内置变量。

除了 int 和 float 外,Python 还支持其他类型的数字,例如小数和分数。Python 还具有对复数的内置支持,并使用jJ后缀指示虚部(例如3+5j)。

3.1.2. Strings

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

注意这里的单引号、双引号和 C 不一样,两者唯一区别就是:外面双引号,里面的双引号就要加\转义。

>>> 'doesn\'t'  # use \' to escape the single quote...
"doesn't"
>>> "doesn't"  # ...or use double quotes instead
"doesn't"

在交互式解释器中,输出的字符串外面有引号,特殊字符不会转义;但print函数略去前后引号,同时转义。

>>> 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来使用原始字符 (raw strings)。

>>> 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

字符串可以跨越多行,一种方法是使用三重引号 """..."""or'''...''',行尾会自动加进字符串,如果不想换行可以在行尾加\

print("""\
Hello \
World
""")

字符串可以用+连接在一起,可以用*重复。彼此相邻的多个字符串会自动连接在一起,但这个特性对变量或表达式无效,要用+

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

字符串可以被索引(用方括号),第一位是 index 0,字符类型都是统一的,每个字符占一位。(index 的复数是 indices) 索引如果是负数,就从字符串的右边开始计数(因为-0 就是 0,从右边的话是-1 开始)。

除了索引,字符串还支持切片 (slicing):索引获取单个字符,切片获取子字符串。 切片总是包括开始,不包括结尾,因此s[:i] + s[i:]就是s。 切片有默认值,省略 (omit) 的第一个索引值默认为 0,第二个索引值默认为总长。

记住切片如何工作的一种方法是将索引看作字符之间的指针。

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

超出长度的索引会报错,但在切片里会返回空。

Python 的字符串不能被更改 (immutable),给字符串指定位置赋值 (assignment) 会报错。

内置函数len()可以返回字符串的长度。

See also:

  • Text Sequence Type — str Strings are examples of sequence types, and support the common operations supported by such types.
  • String Methods Strings support a large number of methods for basic transformations and searching.
  • Formatted string literals String literals that have embedded expressions.
  • Format String Syntax Information about string formatting with str.format().
  • printf-style String Formatting The old formatting operations invoked when strings are the left operand of the % operator are described in more detail here.

3.1.3. Lists

Python 有很多复合 (compound) 数据类型,用来将数据组合到一起,最常用的是 list,是在方括号里用逗号分隔的列表。列表可以包含不同类型数据,但一般是相同类型。

与字符串(以及其他内置的序列类型 built-in sequence types) 一样,list 可以被索引和切片 (index and slice)。

所有切片操作都返回一个包含所请求元素的新列表。这意味着以下切片将返回列表的浅副本:

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

list 也支持串联 (concatenation),就是用加号连接。

与字符串不同,列表是可变类型,即可以更改其内容。不可变对象不能修改只能替换,所以要开新的空间。

可以通过使用append()方法在列表的末尾添加新项。

也可以给切片赋值,这样可以更改 list 的大小,甚至清空。

内置函数len()同样适用于 list。

列表支持嵌套 (nest) 。

3.2. First Steps Towards Programming

当然,Python 也不止 1+1,下面例程给出斐波那切数列的初始子序列 (an initial sub-sequence of the Fibonacci series):

>>> # 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
2
3
5
8
  • 第一行、最后一行的多重赋值:先计算等号右边,再赋值
  • 任何非零数都为真,长度非零就为真
  • 缩进是 Python 组织语句的方式

4. More Control Flow Tools

4.1. if Statements

>>> x = int(input("Please enter an integer: "))
Please enter an integer: 42
>>> if x < 0:
...     x = 0
...     print('Negative changed to zero')
... elif x == 0:
...     print('Zero')
... elif x == 1:
...     print('Single')
... else:
...     print('More')
...

4.2. for Statements

不像 Pascal 只能遍历数字,也不像 C 那样能定义遍历步骤和条件,Python 按顺序遍历任何序列。For example (no pun intended): 英文双关了,哈哈。

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

一边遍历一边修改可能会出问题,一般都是拷贝一份。

4.3. The range() Function

遍历数字的时候,内饰函数range()很方便 (comes in handy)。指定结尾是不出现在序列里的,range(10)生成 0-9。当然也可以调整步进 step。

range起点终点步进

如果你想直接 print 的话,会有奇怪的事情:

>>> print(range(10))
range(0, 10)

range()返回的对象的行为看起来是一个列表,但实际上并非如此。它是一个对象,当您对其进行迭代时,它会返回所需序列的后续项,但它并没有真正组成列表,从而节省了空间。

我们把这种对象称为可迭代的 (iterable),即,函数和结构能从中获得连续的 (successive) 项直到耗尽。for就是这样的结构,sum()就是一个函数的例子。

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

那么如何从range()获得 list 呢?

>>> list(range(4))
[0, 1, 2, 3]

4.4. break and continue Statements, and else Clauses on Loops

Clauses 从句

和 C 语言一样,break能跳出最内层的forwhile循环 (the innermost enclosing for or while loop)。

for遍历结束或者while条件为负时,都会经过else(使用try时没有异常发生也会),但break不会。

continue 语句也是借鉴自 C 语言,表示继续循环中的下一次迭代。

4.5. pass Statements

pass语句啥也不做,常常是语法上需要,但逻辑上不需要的时候用他。

几个例子:

  • 等待键盘中断

    >>> while True:
    ...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)
    ...
    
  • 创建类

    >>> class MyEmptyClass:
    ...     pass
    ...
    
  • 占位符,让你在更抽象层次先思考架构

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

4.6. Defining Functions

关键字 def 引入一个函数定义。它后面必需跟函数名称和带括号的形参列表 (the parenthesized list of formal parameters)。构成函数体的语句从下一行开始,并且必须缩进 (indented)。

函数主体的第一条语句可以是字符串文字 (string literal),这是函数的文档字符串 (docstring)。

函数的执行会引入一个新的符号表,用来存储局部变量。变量引用的时候先在本地符号表中查找,然后是闭包函数的符号表,接着是全局符号表,最后是内置符号表。因此,全局变量和闭包函数的变量不能在函数内部赋值(可以被引用),除非使用了global或者nonlocal

函数被调用时,实参会被引入到局部符号表中,参数的值始终是对象引用。当一个函数调用另一个函数时,也会为其创建一个新的局部符号表。

函数定义会将函数名称与函数对象在当前符号表中进行关联。其他名称也可指向同一个函数对象并可被用来访问访函数。

return语句会从函数内部返回一个值。不带表达式参数的 return 会返回 None。函数执行完毕退出也会返回 None

最后插播一个关于闭包的博客,很清晰——谈谈自己的理解:python 中闭包,闭包的实质

主要问题在于外函数绑定来的临时变量,内函数不能修改。 这件事情也和 Python 的解释原理有关。在解释器开始执行的时候,会在内存中开辟⼀个空间,每当遇到⼀个变量,就把变量名和值记录下来;但是当遇到函数定义的时候,解释器只是把函数名读入内存,表⽰这个函数存在了,⾄于函数内部的变量和逻辑,解释器是不关⼼的。只有当函数被调⽤的时候,解释器才会根据函数内部声明的变量来开辟变量的内部空间。

这里也附一个自己的测试代码,这里globalnonlocal都是不可缺少的。

def outer(a):  # outer 是外部函数
    b = 10
    def inner():  # inner 是内函数
        nonlocal b  # 外函数的临时变量
        global c  # 全局变量
        print(a + b)
        b += 10
        c += 1
    return inner  # 外函数的返回值是内函数的引用
    # 外函数结束的时候发现内部函数将会用到自己的临时变量,临时变量就不会释放,会绑定给这个内部函数

if __name__ == '__main__':
    c = 99

    demo = outer(5)
    print(outer)
    print(demo)
    demo()  # 15
    demo()  # 25

    demo2 = outer(7)
    print(outer)
    print(demo2)
    demo2()  # 17

4.7. More on Defining Functions

也可以使用可变数量的参数定义函数,有以下三种形式。

4.7.1. Default Argument Values

最有用的是指定一个默认参数。注意默认值是在函数定义阶段计算的,所以下面代码输出 5.

i = 5

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

i = 6
f()

4.7.2. Keyword Arguments

函数也可以用kwarg=value的关键词参数来调用,顺序不重要,不能对同一个参数多次赋值。

特别的是,当存在一个形式为 **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. Special parameters

一般来说参数可以按照位置或者关键词方式传递给 Python 函数,但是开发时候可以限制参数传递的方式。

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

注意/*符号,当然这是可选的。

关键字参数也称为命名参数。Keyword parameters are also referred to as named parameters.

这里还有些细节,用到的时候再查。

一个特殊的例子:

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

因为参数名name**kwds的内容可能存在冲突,所以任何调用都不能让他返回True。如(下面这两种写法等价):

foo(1, **{'name': 2})
foo(1, name = 2)

解决办法是把name改成仅位置参数,就不会产生歧义了。

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

Recap(概括):

  • 如果希望形参名称对用户来说不可用,则使用仅限位置形参。这适用于形参名称没有实际意义,以及当你希望强制规定调用时的参数顺序,或是需要同时收受一些位置形参和任意关键字形参等情况。

  • 当形参名称有实际意义,以及显式指定形参名称可使函数定义更易理解,或者当你想要防止用户过于依赖传入参数的位置时,则使用仅限关键字形参。

  • 对于 API 来说,使用仅限位置形参可以防止形参名称在未来被修改时造成破坏性的 API 变动。

4.7.4. Arbitrary Argument Lists

arbitrary 任意的,武断的

最后一种是可变参数 List,这些参数会包装在元组 tuple 中。可变参数前面可能会有常规参数,后面只能是关键字参数。

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

注释:join()方法返回通过指定字符连接序列中元素后生成的新字符串。

4.7.5. Unpacking Argument Lists

解包参数列表,使用*从 list 或者 tuple 中获取单独的参数,同理使用**解包 dictionary。

>>> 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]

4.7.6. Lambda Expressions

可以用 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')]

这个例子很有意思,sort方法支持一个key参数,用来选择进行比较的元素。因此这句话执行的时候依次传入 pairs 的每个元素,然后用lambda函数切片,取出(1, 'one')里的'one'等,最后英文字母顺序排列。

更多关于 Lambda 表达式: Lambda 表达式有何用处?如何使用? - 涛吴的回答 - 知乎 https://www.zhihu.com/question/20125256/answer/14058285

4.7.7. Documentation Strings

Here are some conventions (约定) about the content and formatting of documentation strings.

第一行大写字母开头,句点结尾。

第一行是函数简短精确的目的介绍。简洁起见,不应该显式声明 (explicitly state) 名称和类型,因为这些可以通过其他方式获得。

如果有很多行,那么第二行应该空出来,在视觉上分离。后面可以写函数的调用约定、副作用等。

Python 解析器 (parser) 不会删除多行字符串的缩进 (strip indentation)。缩进的约定是:第一行之后的第一个非空行确定整个文档的缩进量(不用第一行的原因是它通常和字符串开头引号相邻,不明显)。之后所有行都遵守这个缩进。最后 Tab 转化为 8 个空格。

一个例子:

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn't do anything.
...     """
...     pass

4.7.8. Function Annotations

函数标注是关于用户自定义函数中使用的类型的完全可选元数据信息,以字典的形式存放在函数的 __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'

4.8. Intermezzo: Coding Style

intermezzo 间奏曲,插曲

PEP 8 has emerged as the style guide that most projects adhere to(遵循,坚持).

这里摘录几个重要的:

  • 使用四格缩进,而不是Tab
  • 及时换行,不超过 79 字符
  • 使用空行分隔函数和类,以及函数内部的代码块
  • 把注释放到单独的一行
  • 写 docstrings
  • 在操作符两边、逗号后面使用空格,但不要在括号内用
  • 命名规范统一,使用UpperCamelCase来命名类,而以lowercase_with_underscores来命名函数和方法,始终应使用self来命名类的第一个方法
  • 如果你的代码旨在用于国际环境,请不要使用花哨的编码。Python 默认的 UTF-8 或者纯 ASCII 在任何情况下都能有最好的表现
  • 如果不是有外国人要阅读维护代码,不要在标识符用 non-ASCII characters