python笔记

基于 Python 官方学习文档的学习笔记。

学习应更重思想而非实现,编程更多是工具而非结果。变量的命名之于编程思想的学习并不重要,但在实现过程中变量的命名因常常受到忽略从而产生很多问题,所以形成好的、符合一定风格的科学的变量命名习惯就非常重要。另外数据类型反映出语言设计的根本(所谓万物皆对象),因而这两点单拎出来,写在前面

A. 变量命名

  1. 驼峰式命名法(Camel Case)
    • 小驼峰式(lowerCamelCase):第一个单词的首字母小写,后续每个单词的首字母大写。例如:userNameisUserLoggedIn
    • 大驼峰式(UpperCamelCase),也称为帕斯卡命名法(PascalCase):每个单词的首字母都大写。通常用于类命名:MyClassUserAccount
  2. 蛇形命名法(Snake Case)
    • 单词之间用下划线_分隔,所有字母小写。例如:user_nameis_user_logged_in。Python 中主要采用这种命名方式,毕竟蟒蛇也是蛇嘛
  3. 短横线命名法(Kebab Case)
    • 单词之间用短横线-分隔,通常用于URLs或文件名:user-nameis-user-logged-in
  4. 匈牙利命名法
    • 通过在变量名前加上表示类型的小写字母来表示变量类型,例如:iCount(表示整型计数器)、strName(表示字符串类型的名称)
  5. 下划线命名法(Underscore Case)
    • 与蛇形命名法类似,但是首字母也可以大写,通常用于Python中的私有变量:_firstName

不管怎么样不能以数字开头,那么数字本身就可以是一个合法变量名了,为此还要额外判断:变量名不能是纯数字

推荐参考 PEP 8 - Style Guide for Python Code,有对变量命名格式的详细「规范指导」。

但要记得,没有什么金科玉律,没有什么规范是要刻在石头上的,但是在初学阶段通读风格规范文档,牢记并遵循这些约定俗成的规范,一定能够省去很多麻烦,学到许多

B. 数据类型

首先 Python 是属于“动态类型语言”的编程语言,所谓动态,是指变量的类 型是根据情况自动决定的。如在 x = 1 中并没有明确指出“x的类 型是int(整型)”,是Python根据x被初始化为10,从而判断出x的类型为 int的。我们只需赋值和运算,不用管具体的数据类型。

但这常常是十分危险的,尤其是在高维数组的处理中,我们既不清楚一个变量背后的维度,具体的 size,也不清楚他的数据类型,给数据处理带来隐患。可以用注释的形式或调用 py3.5 后引入的类型注解(type annotation)功能提示变量类型。

基本数据类型: 整数型(int)浮点型(float)布尔型(bool)复数(complex)

复杂数据类型:列表(list)元组(tuple)字典(dictionar)集合(set)字符串(str)

列表(Lists):有序的、可变的数据结构,可以包含不同类型的元素,例如:[1, 'a', 3.14]

元组(Tuples):有序的、不可变的数据结构,一旦创建就不能修改,例如:(1, 'a', 3.14)

字典/映射(Dictionaries/Map):无序的(从Python 3.7开始,实际上是有序的)、可变的数据结构,以键值对的形式存储数据,例如:{'name': 'Alice', 'age': 30}

集合(Sets):无序的、可变的数据结构,用于存储唯一的元素,例如:{1, 2, 3}

1.元组,数组和列表

很多编程语言中有数组和列表两个概念,核心区别在于数组 array 中的所有元素类型必须是同类型的所以可以有更高效的存储和计算性能,而列表 list 是更灵活的数据结构可以包含有不同类型的元素

但是 python 没有叫做数组 array 的内置数据结构,这一点靠 python 库 numpy 的函数,提供了数据类型 numpy.array 去实现。np.array 和 list 都用 [1, 2, 3] 这样的形式表示。(括号层数代表数组维度)

当 np.array 的指定数据类型参数 dtype = object 时,数组为对象数组,与列表几乎一致,可以混合包含整数、字符串、列表或其他 python 对象

元组 tuple 是区别于已有的数据类型的一种(好像是),是python 中独有的一种数据类型,他有:元组不可变,一旦被创建,其包含的元素个数(size)和类型就不可变了,而其具体的值是否可变要取决于其元素的数据类型。因为元组的不可变性,元组常被用作词典的键

又有 asarray: array 和 asarray 都可以将结构数据转化为ndarray,但是主要区别就是当数据源是 ndarray 时,array 仍然会copy出一个副本,占用新的内存,但 asarray 不会。

2.可变与不可变

可变(Mutable)与不可变(Immutable)是 Python 中数据类型的基本性质,数据类型可以根据他们是否允许在创建后修改其值来分为可变类型或者不可变类型。整数,浮点数,字符串,布尔值,复数和元组是不可变类型,而列表,字典,集合和数组是可变类型。 一系列类型的数据中只有元组和字符串是不可变类型,单个数据差不多都是不可变类型。

不可变类型意味着一旦创建,它们的值不能被改变。例如,你不能更改一个整数的值或更改一个字符串中的字符。如果你尝试“更改”一个不可变类型的值,实际上会创建一个新的对象。可变类型则允许在创建后修改其内容。例如,你可以添加或删除列表中的元素,或者更改字典中的键值对。(元组也是不可变的,这意味着如果要改变元素的值,虽然不改变类型和大小,但因为不可变性实际上会创建一个新的元组对象)在设计程序时,了解数据类型的可变性是非常重要的,因为它影响数据的共享、传递和存储。不可变类型通常更安全,因为它们保证数据不会被意外修改;而可变类型则在需要动态修改数据时非常有用

3.字典

在Python中,字典(dict)是一种存储键值对(key-value pairs)的数据结构,每个键都映射到一个确定的值,键有几个基本特性:1.唯一性2.不可变性(所以键只能是整数浮点数字符串和元组等不可变类型)3.散列性(hashability)

一个键只能对应于一个值,但是因为值没有太多约束所以多个键可以对应到同一个值。

1
2
3
4
5
6
7
8
9
10
11
# 创建一个有序字典
my_dict = {
"apple": "fruit",
"carrot": "vegetable",
"banana": "fruit",
"celery": "vegetable"
}

# 多个键对应相同的值
my_dict["red"] = "color"
my_dict["blue"] = "color"

字典这种索引映射的方式和类有一定的相似性,但远远不如。

字典是 Python 内置的数据结构,用于存储键值对;而类是用户定义的数据类型,是面向对象编程的核心特性之一,用于创建具有复杂行为和属性的对象。类可以包含方法(函数)和属性(变量),并可以定义初始化方法。

类包含对象的重要特性:封装(Encapsulation),指可以将数据和操作这些数据的方法组合在一起,并隐藏内部实现的细节,只暴露出一个可以被外界访问的接口。通过使用私有属性(__双下划线开头)和公有方法来控制对成员的访问;继承(Inheritance),子类可以继承父类的属性和方法 ;多态(Polymorphism),即同一个接口可以被不同的数据类型以不同的方式实现,关注对象的行为而不是对象的类型,具体遵循鸭子类型(Duck Typing)。

鸭子类型(Duck Typing)是Python中的一种动态类型的特殊形式,它基于这样一个原则:不必关注对象是什么类型,只要它“走起来像鸭子,叫起来像鸭子”,就可以将其当作鸭子对待。这个概念的名称来源于英语中的一句谚语:“If it looks like a duck and quacks like a duck, then it probably is a duck.”

1. Whetting Your Appetite

Motivation.如果你有以下需求:

  • 从事计算机相关工作,有自动化办公需求
  • 多语言多平台融合编程需求
  • 有大量库支持,可扩展的软件编程需求

那么恭喜你, Python is just the language for you!

因为 Python:

  • 可以像 Unix shell script 文件和Windows batch file 文件一样实现系统级的脚本功能,甚至是应用级(shell script和batch做得到吗)。作为脚本语言,Python也有良好的易用性(比 C 和 JAVA 好写)和平台兼容性(可以在 Unix,Windows 和 MacOs 上通用)

  • 不只是脚本语言,Python 是一个完整的编程语言,有完整的数据结构和语法支持,作为一种非常高级的编程语言,Python 比 C 提供了更多的错误检查(Python 是一种动态类型语言,而 C 是一种静态类型语言,Python 在运行时就能够联系前后文检查错误,而对于 C 来说,如果在编译时没有发现错误就只有在运行时才会暴露,这将导致更加难以诊断的问题。Verilog 也是同理,可能在编译过程中不会报错,但在后续的综合过程中产生)

  • 支持把程序分割成程序块并打包成可供调用的模块,并可以实现包和模块的分发,也有许多现有的官方非官方的模块可用

  • 是一种解释型语言(interpreted language),在运行时是由解释器逐行读取并执行的,而非像是编译型语言那样先编译成机器码再运行。因为不需要汇编(compile)和链接(linking)这两个步骤,就可以节省程序开发中的时间,在编写的过程中直接运行代码,而不用等待编译和链接的时间

  • 可以以一种更紧凑的格式进行编写,(同时可读性也更好,)Python 代码通常要比 C 类和 Java 代码要更短一些,因为: 1. 更顶层的编程语言有更强大的函数,可以在单个语句中表达复杂的操作 2.代码块(语法)是靠缩进(indentation)实现的而不是类似于 C 那样用括号包裹起来 3.无需声明变量(variable)或参数(argument)

库包和模块

严格来说,Python 中是只有模块(module)和包(package),而没有库(Library)的概念的。库存在于 C 类语言中,而在 Python 中库就是一个通俗概念,我们平时说的库文件既可以是一个模块也可以是一个包

模块


模块本质上是一个 Python 程序,以 .py 作为后缀。任何 .py 文件都可以是模块。A module is a file ending in .py that contains the code you want to import into your program

其他可作为模块的文件类型还有 .so、.pyo、.pyc、.dll、.pyd

通过使用模块,可以有效地避免命名空间的冲突,可以隐藏代码细节,让我们专注于顶层实现(逻辑),还可以将一个较大的程序分为多个文件,提升代码的可维护性和可重用性


是含有一个 __init__.py 文件的文件夹,这个__init__.py文件本身就是模块,除此之外,一个包内可以有也可以没有其他模块(但是一般推荐有)。它这个模块的作用是将一个文件夹(包)转变为若干个 Python 模块的集合。包(文件夹)内的__init__.py文件可以不含代码(也不推荐),但是这样这个包就什么也做不了,所以一般会包含一些初始化代码,在这个包被import的时候,这些代码会自动执行

1
from mypackage import module1

包内可以嵌套包,包和模块的示例结构:

mypackage/

├── init.py
├── module1.py
├── module2.py
└── subpackage/
├── init.py
└── module3.py

1
2
3
#并且被导入
from mypackage import module1
from mypackage.subpackage import module3

在 import 时,解释器会按照顺序从几个位置寻找这个被 import 的模块:(1)执行文件主程序所在目录(2)Python 的环境变量所在的目录(3)Python 的模块文件夹(一般是 Lib 文件夹)内的 site-pages 文件夹中

Tips

Python 名字来源:巨蟒剧团之飞翔的马戏团

By the way, the language is named after the BBC show “Monty Python’s Flying Circus” and has nothing to do with reptiles. Making references to Monty Python skits in documentation is not only allowed, it is encouraged!

Python的创始人吉多·范罗苏姆,在1982年至1995年间,参与了荷兰数学和计算机科学研究学会多个项目的工作。1989年的圣诞节期间,他决心开发一个新的脚本解释编程,作为 ABC 语言的继承者,并且用它替代 Unix shell和 C 语言来进行系统管理,担负与 Amoeba 操作系统之间的交互操作并进行例外处理。他是 BBC 电视剧《Monty Python》的爱好者,所以选取了 Python 作为这个编程语言的名字

2. Using the Python Interpreter

2.1. Invoking the Interpreter

Python 解释器通常在安装后就可以直接在终端呼出

可以在终端输入指令:

1
Python3.12	# Python3 in Linux

在 Windows 中,有Python Launcher for Windows,可以方便管理 Python 的版本和启动,默认启动最新版本:

1
py

也有推荐 pyenv 作为 Python 的版本管理工具 which follows the UNIX tradition of single-purpose tools that do one thing well.

2.1.1. Argument Passing

Python -h # for help

2.1.2 Interactive Mode

开启

当 tty(Terminal) 在读取,处理我们的指令的这一过程,解释器(interpreter)就进入了所谓的交互模式(Interactive mode),每一个新的命令行以三个大于符号(greater-than sign) >>> 开头;每一个接续的行以三个点(dots)...开头

Python 的欢迎词以版本信息(版本号,版本类型,发布时间,运行环境等)和一些简要的参数提示起手,一个简单的 Python 环境和程序如下:

1
2
3
4
5
6
7
8
C:\Users\xxxx>py
Python 3.12.3 | packaged by Anaconda, Inc. | (main, May 6 2024, 19:42:21) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> the_world_is_flat = True
>>> if the_world_is_flat:
... print("Be careful not to fall off!")
...
Be careful not to fall off!

关闭

可以输入一个文件结束符号(end-of-file character)关闭 Python 的交互模式(Windows 下是Control-D,Unix 下是Control-Z),会使得解释器以零退出状态(zero exit status)退出。或者可以输入执行 quit()

2.2. The Interpreter and Its Environment

2.2.1 Source Code Encoding

Python 源文件视作遵循 utf-8 编码,如果不是默认的 utf-8 应当在第一行声明:

To declare an encoding other than the default one, a special comment line should be added as the first line of the file. The syntax is as follows:

1
2
3
# -*- coding: encoding -*-
# For example use Windows-1252 编码
# -*- coding: cp1252 -*-

只有一种例外,如果代码以 shebang 符号开头,那么编码声明可以放到第二行:

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

Shebang(也称为 Hashbang)是由一个井号和一个叹号构成的字符序列#!,其出现在文本文件的第一行的前两个字符(用于 Unix 和类 Unix 系统中的脚本文件,告诉系统在执行脚本时应该使用哪个程序作为解释器)。在文件中存在Shebang的情况下,类Unix操作系统的程序加载器会分析Shebang后的内容,将这些内容作为解释器指令,并调用该指令,并将载有Shebang的文件路径作为该解释器的参数

ASCII 编码只需要 1 个字节(8 bits,256 levels),而很多其他语言(中日韩语言字符,希腊字母,罗马数字等)则需要远多于 1 个字节的数据,就需要一种能够兼容全部语言的编码格式来满足通用性需求, Unicode应运而生:

utf-8 是 Unicode 的一种实现方式(亦有 utf-7,punycode,utf-6,utf-32,SCSU等实现方式),为了节省内存占用,utf-8 编码是一种可变长度字符编码,针对不同的字符对象有不同的编码长度,如 ASCII 码占用一个字节,拉丁文希腊文占用两个字节,其他大部分字符包括大部分汉字占用三个字节,还有一些少数字符占用四个字节


关于解释器(Python Interpreter)

Python 解释器的官方实现是叫做 CPython 的,顾名思义,CPython 是使用 C 语言实现的 Python 解释器。作为官方实现,它是最广泛使用的 Python 解释器。除了 CPython 之外,还有使用 Java 实现的 JPython,用 .Net 实现的 IronPython,使 Python 可以更容易方便地与 JAVA 或 .Net 等程序集成

相比于 CPython 这个标准实现,另一种著名的实现叫做 PyPy,因为 PyPy 是一种即时编译器,而 CPython 是一种解释器,所以它比 CPython 运行的要快得多。同时大多数 Python 代码都可以很好地运行在 PyPy 上,除非代码依赖于一些 CPython 扩展

PyPy 在内部使用叫做元跟踪的技术,它将解释器变换成跟踪即时编译器。因为解释通常比编译器要容易写,但运行得更慢,这种技术可以更容易的产生出编程语言的高效实现。PyPy 的元跟踪工具链叫做 RPython。

1
pypy/pypy3 -m ensurepip # install 'pip'

3. An Informal Introduction to Python

在 Python 编译器内,>>>...被用来区分输入和输出, # 用来注释其后面的内容(告诉解释器 hash 后面的内容可以忽略,不需要参与解释,编译),但不能用在字符串内,字符串内的井号就是井号本身

示例如下:

1
2
3
4
# 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. Using Python as a Calculator

3.1.1. Numbers

Python 支持基本的加减乘除运算符号和括号(parenthese),用于分组(grouping)

根据数据内容分配数据类型:整数有int,小数(浮点数)有float

示例如下:

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

这里一个斜杠 / 用作除法(division)求商得到的永远是浮点数;双斜杠 // 求商得到的结果是向下取整(floor division),也叫地板除,得到的结果会向下取整,负数结果会更接近负无穷;百分号%可以用来表示取模运算,即取余

例如:

1
2
3
4
5
6
7
8
9
>>> 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

还有幂运算(power calculation)用**表示,=用于表示变量的赋值(定义),如果一个变量没有被赋值(定义),这个变量就不可用,混合运算(内含小数的)的结果会被转换成浮点数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 5 ** 2  # 5 squared
25
>>> 2 ** 7 # 2 to the power of 7
128

>>> 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 当做一个计算器,利用这个下划线继续进行运算:

1
2
3
4
5
6
7
8
>>> tax = 12.5 / 100
>>> price = 100.50
>>> price * tax
12.5625
>>> price + _
113.0625
>>> round(_, 2)
113.06

下划线变量只读(read only),如果要给他赋值,会产生一个有相同名字的 local variable ‘Don’t explicitly assign a value to it — you would create an independent local variable with the same name masking the built-in variable with its magic behavior.’

3.1.2. Text

文本(字符串)性质


Python 可以利用其内置的数据格式 str,strings(字符串)来操作文本数据。包括中文,英文,特殊字符,空格都属于字符串的范畴并且占位

字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,只能重新创建一个新的字符串对象

用引号包裹内容以表示字符串,对于 Python 来说,字符串允许单引号和双引号(甚至允许三引号),解释器可以区分单引号和双引号,单双引号可以混用,但如果想要在字符串内包含引号本身,则需要换用与与包裹字符串不同的引号,或者加转义符;也可以把字母 r(raw) 放在字符串的第一个引号之前,表示保留原格式(可以保留特殊字符本身,避免各种特殊字符发挥意想不到的组合作用作用)

三引号是用来表示多行字符串的,尤其是当字符串太长,一行写不下(end of lines are automatically included in the string,也可以自己手动一行行加来):

1
2
3
4
5
6
7
>>> hi = '''hi 
there'''
>>> hi # repr()
'hi\nthere' # newlines among chars are detected as\n
print hi # str()
hi
there

也还有一种情况叫做文档字符串(docstrings) 用于解释函数、方法、类或模块用途的多行注释。他们被三引号包裹并放在函数的开头。文档字符串通常可以通过内置函数的 help() 或者对象的 __doc__ 来访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# |"""| instead of |'''| ok as well

def add_numbers(a, b):
'''
This function adds two numbers together.

:param a: First number
:param b: Second number
:return: Sum of the two numbers
'''

# call function docstrings
print(help(add_numbers))

# call fuctions __doc__ object
print(add_numbers.__doc__)

字符串链接


在 Python 里字符串的链接(concatenated / glued together)操作非常方便:对于(当且仅当)两个裸露的纯字符串(非变量非表达式)来说,可以直接把他们放在一起;如果有非纯字符串存在,就只能用 + 把他们链接,或者用 * 表示重复

值得注意的是,哪怕是都有的混合表达式,只要其中有两个挨着的纯字符串,就可以不用符号链接

加减乘除符号中就只有加和乘可用,分别表示链接和重复,减和除法不可用,会报错显示:TypeError: not all arguments converted during string formatting

例如:

1
2
3
4
5
6
7
8
9
10
11
>>> 'Py' 'thon'
'Python'
>>> # "particularly" useful for combine string in muti-lines
>>> text = ('Put several strings within parentheses '
... 'to have them joined together.')
>>> text
'Put several strings within parentheses to have them joined together.'

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

混合时必须要用 + (包括切片结果):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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'

>>> prefix + 'thon' '2nice' # work as well
'Python2nice'

字符串索引与切片


字符串就像数组一样,可以进行索引(index / subscripted)和切片(slice)

在 Python 中,字符串数组列表元组的处理逻辑都是一样的,都是从第 0 位开始,依次向右递增,而最后一位也可以视为是 -1,依次向左递减,不管是正数还是负数,都不可以超过字符串大小的范围。在字符串内不管是数字字母还空格还是特殊符号都占位

因为 0 就是 0 不存在正负性,所以 -0 在 Python 中视作 0 本身,正数从 0 开始,而负数索引(negative indices)从 -1 开始

切片是用数组形式,左闭右开

1
2
3
4
5
>>> word = 'Python'
>>> word[0:2]
'Py'
>>> word[2:5]
'tho'

对于左闭右开也有一种理解,切片数组内的数字不代表对象位置(很难想象数组内左右两个数字意义不对等),而是**”切”片里面要切**的位置,第 0 个”对象”之前的位置代表 0,正中间,然后向两边依次分第几层

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

Python 也有关于切片的内置函数或 numpy 函数 sliceslice() 函数实现切片对象,主要用在切片操作函数里的参数传递。

1
2
3
4
5
6
7
8
9
10
11
>>> # class slice(stop)
>>> # class slice(start, stop, step)

>>>myslice = slice(5) # 设置截取5个元素的切片
>>> myslice
slice(None, 5, None)
>>> arr = range(10)
>>> arr
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> arr[myslice] # 截取 5 个元素
[0, 1, 2, 3, 4]

内置函数(built-in function)len()可以描述字符串长度,切片长度恰好是起止位置两数之差。

我个人习惯用单引号表示字符串,这样看起来更简洁(在这个文档内也多用单引号),在实际使用的时候更多在写一段话时用双引号在引用数据时用单引号

关于单引号和双引号曾在著名插件 Black Formatter 的 118 号 issue 发布并引发大量讨论

3.1.3. Lists

列表(List)是 Python 中最通用(versatile)的分组类(to group together other values)的数据类型;列表内的数据通常用方括号(square bracket)包裹,组内数据用逗号隔开;通常列表内是同类数据,当然也可以不同

列表的形式和操作方法几乎和字符串一模一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> list_1 = [1, 2, 3]
>>> list_2 = ['a', 'b', 'c']
>>> list_3 = ['physics', 'chemistry', 97, 2018]

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

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

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

包括内置方法len()也能用;

所以除了它是方括号括起来的而非引号,可以内含多种不同数据之外,就只有一点主要不同,但也是最重要的一点不同:

字符串是不可变类型,列表是可变类型

不可变


正如前面所描述的,可变类型就是后续可以改变值的数据类型而不可变类型就是后面不可以再改变值的数据类型,所以修改字符串本质上会保存到一个全新的字符串里,而列表是真的在它的基础上修改:

1
2
3
4
5
6
7
>>> rgb = ["Red", "Green", "Blue"]
>>> rgba = rgb
>>> id(rgb) == id(rgba) # they reference the same object
True
>>> rgba.append("Alph")
rgb
["Red", "Green", "Blue", "Alph"]

所以我们可以认为对于列表的赋值,他就是相当于给这个列表增加了几个”别名”(浅拷贝 shallow copy),修改其中的不管哪一个,都是在对同一个对象进行操作(而不可变类型的数据因为不可变所以是不是同一个对象,反正不能操作),从 append看出这个操作甚至可以改变列表的 size,因此索引和切片的操作也可以同步改变。虽然切片改变值可以但是切片赋值列表不行,你可以通过slice改变或者删除几个值(整个删掉或者替换掉都没关系),也可以用append增加几个值,但如果用切片的部分去赋值,那就是全新的列表了(深拷贝 deep copy),哪怕用切片的全部去去赋值也不行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> correct_rgba = rgba[:] # even all elements included, the sliced results keeps not origin
>>> correct_rgba[-1] = "Alpha"
>>> correct_rgba
["Red", "Green", "Blue", "Alpha"]
>>> rgba
["Red", "Green", "Blue", "Alph"]

>>> abc = ['0', '1', '2', '3', '4']
>>> def = abc # we know that id(abc)==id(def) is true
>>> def = ['a', 'b']
>>> abc
['0', '1', '2', '3', '4'] # abc stay unchanged,here abc is not same obj as def

>>> def = abc
>>> def[: ] = ['a', 'b']
>>> abc
['a', 'b']

从这个例子看如果不是调整列表,而直接赋值的话,就相当于把这个列表名(别名)套给了别的列表,比如这里的 def = ['a', 'b'] 就是把def从之前的列表换绑到这个新的列表;而如果def[: ] = ['a', 'b']就不是换绑而是调整

浅拷贝就是复制对象本身和包含的元素,但不复制实际对象,他们共享同一个对象,只是复制了索引到一个新的别名,而相对的深拷贝就是创造一个完全独立的副本,对象数值相同但不是共享,原对象和深拷贝对象没有任何共享的子对象。深拷贝就是深度拷贝出一个全新的独立的相同的对象而浅拷贝就是浅浅的拷贝,实际上没有新的东西,旧名新名共享内容

嵌套


列表支持嵌套(nest)结构,可以借此生成”高维”列表(可索引):

1
2
3
4
5
6
7
8
9
>>> 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'

这里嵌套得到的”高维”列表可以不是整齐的,以”二维”列表为例,其中的每个列表不需要有相同的 size

3.2. First Steps Towards programming

while 函数


这三类数据,数字、字符串、列表是非常重要的,但是 Python 远不止能做加减乘除或者拼接字符串这样而已,Python 是可以执行很多复杂操作的,比如我们可以写一个函数(还没写到函数)一段代码生成斐波那契数列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> # 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

从这个简单的例子我们就能看到 Python 的一些强大功能:

  • 可以同时赋值好几个变量,比如这里的第一行和最后一行,等号左边是变量,右边是值
  • 循环中只要判断条件是 true 就能够循环下去,和 C 类语言一样 0 代表 False 1 代表 True,空代表 False,有东西代表 True, 判断符号也相同:> 大于、< 小于、 >= 大于等于、<= 小于等于、 == 等于、!= 不等于
  • 缩进是 Python 用来判断程序块的方式,相当于 C 类语言中的大括号,缩进的层级指示代码块的逻辑层级
  • 打印(Print)函数可以同时处理打印多种不同的数据类型
1
2
3
4
5
6
7
8
9
10
i = 256*256
>>> print('The value of i is', i) # deal with string and var(int) in the same timne
The value of i is 65536

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

4. More Control Flow Tools

就如同上一章的 while() ,这一章介绍常用的 Python 方法/函数:

4.1. if Statements

if 表达式基本组成为 if 判断语句: 执行语句 elif 判断语句: 执行语句 elif 判断语句: 执行语句 else 执行语句: 执行语句

其中最后一个 else 是表示所有其他情况,所以不需要加判定条件; elif 可以出现 0 到 n 次,是可选项;keyword 是 ‘else if’ 的简称,简称为四个字母可以使靠缩进区分功能的 Python 看起来更整齐

1
2
3
4
5
6
7
8
9
10
11
12
>>> 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')
...

if 表达式用于有表达式形式的判定条件,而如果是有一个值和几个数作对比,或者判断几种特定的情况,就更适合用 [match statement](#4.7. match Statement)

4.2. for Statement

Python 的 for 表达式和 C 或者 Pascal 的有一点点不同,Pascal 可以设置循环次数,C 可以设置步长和终止条件(应该是说都必须数字运算作为判断标准,处理对象可以是这个数字变量本身,数字变量也可以仅作为 index)。而 Python 的 for 表达式可以遍历一个(一维)序列内的元素(列表或者字符串都可以):

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

不过如果在遍历(比如)列表内的元素并且做一些操作时,常常会改变列表的元素本身(save),所以我们最好备份(save as),然后只修改副本(copy):

1
2
3
4
5
6
7
8
9
10
11
12
13
# Create a sample collection
users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'} # diction with key and value

# 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

并且,Python 的 for 只是遍历这个对象并不会记录序号,可以用 enumerate() 得到返回的序号值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# usage of enumerate()
>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1)) # 下标从 1 开始
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]

# ugly with a additional var i
>>> i = 0
>>> seq = ['one', 'two', 'three']
>>> for element in seq:
... print i, seq[i]
... i += 1
...
0 one
1 two
2 three

# elegent enumerate include built-in index
>>> seq = ['one', 'two', 'three']
>>> for i, element in enumerate(seq):
... print i, element
...
0 one
1 two
2 three

这时候我觉得有个问题,从给出的例子来看,如果 for 或者 enumerate 的对象不只是一维的,是二维甚至更高维,会怎么样(这给出的例子是字典或者是成对的,相当于是一维,并且输出有顺序性)

事实上 for 处理的对象只能是一维的,如果要面对二维或者高维的数组/列表/字符串就只能使用嵌套结构分别处理

实际上生成也是需要分别处理:

1
2
3
4
5
6
7
8
9
>>> lists = [[] for bracketIndex in range(3)]
>>> for obj1 in range(3):
... lists[0].append(obj1)
>>> for obj2 in range(5):
... lists[1].append(obj2)
>>> for obj3 in range(7):
... lists[2].append(obj3)
>>> print('lists is:', lists)
lists is: [[0, 1, 2], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]

range(3) 可以生成一个整数列表对象,这个函数将会在下个部分介绍,但是在此之前可以看到这个空列表的生成方式很有意思

推导式(Comprehensions in Python)


这里数组的生成方式叫做列表推导式(list comprehension)

同样的,也有字典推导式(Dictionary Comprehension)、数组推导式(Array Comprehension)、元组推导式(Tuple Comprehension)、集合推导式(Set Comprehension)

以列表推导式为例:

1
2
3
4
5
6
>>> # Syntax of list Comprehension
>>> List = [expression for variable in object if condition]

>>> # Syntax of for Statement
>>> for variable in object
expression

其中 if 表达式不是必须的,可以用来作条件筛选也可以,不用也可以

和 for 表达式语句对比非常相似,只是调整了表达式的位置,for 语句是在遍历可迭代对象并执行表达式语句设计次数,而列表推导式是在迭代次数上重复(执行)之前的语句,生成设计长度的列表

for 语句的目的是对指定对象执行指定次数的操作,而推导式的目的是生成指定内容的(可迭代)对象

具体实现来说,推导式甚至可以支持多层 “嵌套”,并且加入判断条件,比如:

1
2
3
4
5
6
7
8
9
10
>>> list_3d = [[x, y, z] for x in range(2) for y in range(3) for z in range(5)]
>>> # This 3d list contains 2*3*5 = 30 items

>>> # Comprehension with if
>>> source_x = [30, 12, 66, 34, 39, 78, 36]
>>> source_y = [3, 5, 7, 11]
>>> result = [(x,y) for x in source_x for y in source_y if x % y == 0]
>>> print(result)

[(30, 3), (30, 5), (12, 3), (66, 3), (66, 11), (39, 3), (78, 3), (36, 3)]

同样的,其他的推导式语法完全相同,但要符合相对应的数据类型:

1
2
3
4
5
6
7
8
>>> List = [expression for variable in object if condition]
>>> Tuple = (expression for variable in object if condition)
>>> keyList = ['','']
>>> Dictionary = {key:keyValue for key in keyList if condition}
>>> Set = {value for variable in object if condition}

>>> print(Tuple)
<generator object <genexpr> at 0x7faf6ee20a50>

其中元组推导式生成的不是元组,返回的是生成器对象,需要 tuple() 函数做转换,并且生成器对象会在遍历后消失。

集合对象和字典对象都需要用大括号,区别是字典需要是键值对的形式而集合不必,集合内保存的元素必须唯一。

4.3. The range() Function

4.7. match Statement

To be continued: …

怎么理解 Python 万物皆对象

分发 是什么,怎么做

爬虫

机器学习

前后端

https://docs.python.org/3.12/tutorial

https://docs.python.org/3.12/library/copy.html#shallow-vs-deep-copy


python笔记
http://pafl.top/2024/07/03/python笔记/
Author
Paf
Posted on
July 3, 2024
Licensed under