目录
0x00 前言
这篇教程是写给已经学会c++、java等面向对象语言的人快速入门Python的教程,这篇教程的目的是为了减少学习Python过程中不必要的时间,因此这里假定读者已经明白了计算机和编程语言中的一些基础概念,本文不再复述这些概念。
如果对哪些概念不太清楚,请善用搜索引擎。
在这个前提下,本文可能会比较硬核,相比一些初学Python的书籍,本教程一方面会大幅减少解释的文字,直接类比其他语言的特性,甚至直接不展示一些显而易见的输出结果,另一方面对每个章节都会多塞一些深入但常用的知识点。不过不用担心,Python这门语言非常简单,在有编程基础的前提下,你很快就能掌握它。
Python有着丰富和强大生态,几乎任何常见的操作你都能找到相应的模块帮助你编写程序。但是,在编写一般的程序时,请不要首先去寻找是否已经有现成的类库,否则你除了能够“三行代码写一个网站”、“十行代码写一个游戏”以外,将不会有任何收获。使用Python是为了让你在编程时不受限于编程语言本身,而把注意力放在解决实际问题上。因此,为了防止你的编程能力降低,对于一般的程序,第一次编写建议时先自己思考怎么解决。另外,不建议将Python作为你的首要编程语言。
0x01 安装与配置
这里以 Windows 平台为例。
首先打开 Python官方网站的下载页面,点击Download Python x.x.x即可。下方也有不同Python版本可选,本文基于Python 3.9.13,读者可以选择更新的版本。
为方便起见,可以不更改安装目录,直接一路点击下一步完成安装即可。
设置环境变量
在Windows平台,默认情况下,Python安装位置为C:\Users\你的用户名\AppData\Local\Programs\Python\Pythonxx
,将这个地址添加到环境变量中即可。
为验证环境变量是否配置正确,可以打开命令提示符并输入python --version
(或python3 --version
,以下使用python
指令)。
常见问题
在Windows10的命令行输入python
并回车时,可能会出现弹出微软应用商店的迷惑现象(或者执行python --version
时无反应)。如果出现这种情况,可以看这篇文章。
使用python
命令会卡很久,经常卡几个小时都不动。遇到这种情况可以检查一下环境变量,有时候在安装时让Python自动设置环境变量,可能并没有设置正确。
编辑工具
Python代码使用VSCode、PyCharm等IDE都可以,Eclipse、Visual Studio等老牌IDE均有插件用来编辑Python代码,这里推荐使用PyCharm,其中社区版是免费的。
0x02基础知识
1. 快速上手
"Hello World"
创建一个main.py
的文件并打开它,开始编写喜闻乐见的第一行代码,打印Hello World:
print("Hello World")
仅需一行代码,无需主函数之类的。有点像shell,你可以把Python理解为一个脚本语言,只不过它很强大,它的代码简洁而优雅,而且比一般的脚本语言可以做更多的事。
虽然如此,还是建议加个判断语句做保护,到后面会说明为什么:
if __name__ == '__main__':
print('Hello World')
为了节省篇幅,后面的示例代码中不一定会加上这个判断条件,但最好养成习惯加上它。
为了执行它,你可以使用命令提示符并定位到该文件所在的目录下,并输入python main.py
。
加点变量和运算
在Python中,变量无需声明类型,直接使用名称并赋值即可。
a = 1
b = 2
print(a + b)
在Python命令行中编写
Python也可以不写在文件中。打开命令行,输入python
并回车,即可进入到python命令行。
接着输入a=1
并回车,即可定义变量a并赋值1.
再定义一个变量b=2
,并输出a+b的值:
C:\Users\LSeng>python
>>> a = 1
>>> b = 2
>>> print(a + b)
3
你也可以直接输入a + b
以输出它的值:
>>> a = 1
>>> b = 2
>>> a + b
3
这种方法的输出,会自动记录到_
中,你可以用它方便的做运算。
>>> 1 + 2
3
>>> _ * 2
6
使用Python命令行执行指令可以在不创建文件的情况下快速完成一定的操作,熟练了之后你可以在极短时间内实现你想要的临时操作,比如批量更改文件名,使用爬虫随便爬点东西等等。
为了节省篇幅,后面的短代码和运行结果会使用这种方式表示。
字符串
Python的字符串可以用单引号或双引号括起来,比如
str1 = "abc"
str2 = 'edf'
str3 = "I'm Peter."
用哪个引号开始就要用哪个引号结束,且可以在字符串内使用另一种引号,比如上面的str3
。当然,你也可以用转义字符\'
、\"
来在字符串内表示引号。除此之外Python还支持换行\n
,制表符\t
,反斜杆\\
等常用的转义符。
在字符串的前引号前加上r,就可以忽略转义字符。
>>> print(r"abc\def")
abc\def
在字符串前引号前加上f,可以在字符串内使用变量或表达式:
name = "Peter"
score = 68
print(f"{name}'s score is {score / 10}.")
运行结果
Peter’s score is 6.8.
在Python3.5之前,则需要使用"{}'s score: {}".format(name, score / 10)
r
和f
等还可以连用,比如
print(rf'Hi, {name}. \(^o^)/')
可以使用加号+
进行字符串连接,实际上如果是两个用引号确认的字符串常量的连接,连+
号都不需要。
print('abc'+'def')
print('123'+'456')
print('Life is like a box of chocolate, '
'you never know what you are gonna get.')
在上面第三个print中,将字符串分成两行以便提高超长字符串的可阅读性。同时,换行后的字符串前引号与上一行的字符串的前引号对齐,这样的代码规范可以提高代码的可阅读性。
Python还提供了这样的语法糖,使用乘法构造字符串:
>>> s = '-' * 20
>>> s
--------------------
代码规范及 PEP 8
PEP 8是一个代码风格规范,它规定了一系列要求来让程序员写出一定风格的代码。在实际的开发特别是团队开发中,使用一致的代码风格写出高可阅读性的代码可以提高开发效率。PEP 8的内容非常多,你可以在这里看到它的详细内容。不用担心记不住这么多规范,如果你使用的是PyCharm来编写python代码,在写出不符合PEP 8的代码时,它会提示你而且可以一键修改。在后面的内容中也会穿插相关的建议来帮助你写出好看的代码。
部分PEP8的内容
- 每行代码控制在80个字符以内。在大部分代码编辑器中都会有一条竖线告诉你第80个字符在哪里,你只要不超出这条竖线即可。如果代码太长,可以使用
\
来换行result = varible1 + varible2 + varible3 + \ varible4
- 不要使用制表符(Tab)作为缩进,每一级缩进应该使用4个空格来表示。大部分代码编辑器在编辑python代码时,tab键的缩进都是4个空格。
- 在文件的最后留一行空行。
- 二元运算符两边留一个空格,比如
a + b
而不是a+b
- …
注释
单行注释使用#
开头。PEP 8建议在#
后加一个空格然后再编写注释。如果注释要写在某行代码的右边,就在#
前空两个空格再写。
print('Hello World') # 这是注释
多行注释可以使用连续的三个引号(单引号和双引号都可)起始和结尾。
'''
这里是多行注释的示例
作者:LSeng
时间:2022年7月19日
'''
print("Hello World")
它还可以作为多行字符串使用,但不建议这样用,因为为了去掉字符串中首尾的换行和每行前面的空格会导致代码很难看:
if __name__ == '__main__':
print('''
Hello
World
''')
str = """ \
line 1
line 2
line 3\
"""
print(str)
运行结果
Hello
World
line 1
line 2
line 3
用户输入
使用函数input(mes)
即可要求用户输入,得到的内容是字符串,mes为给用户输入的提示。
name = input("Tell me your name:")
print(f"Your name is {name}")
如果需要将输入的内容转化为整数,可以使用int()
:
age = input("Tell me your age:")
if age >= 18:
print("You are an adult")
2. 变量与运算
什么是动态语言?跟静态语言有什么区别?
以Python为例,Python定义的变量不需要指定类型,而且可以让同一个变量指向不同类型:
a = 1
print(type(a))
a = "abc"
print(type(b))
运行结果
int
str
对于函数来说,有点像其他语言中的泛型?
def func(a, b):
return a + b
print(func(1, 2))
print(func("Hello", "World"))
print(func("Hello", 1))
运行结果
3
HelloWorld
TypeError: Unsupported operand type(s) for +: ‘int’ and ‘str’.
动态类型的语言是指在运行期间才去做数据类型检查的语言,而静态语言是在编译时做数据类型检查。动态语言的变量在运行时才能确定具体类型,和静态语言在编译时就已经确定类型了。
除此之外还有强类型定义和弱类型定义的区别。
对于强类型语言,某个数据类型的变量如果不经过强制转换,那么就永远是给定的数据类型。
Python属于动态强类型定义语言,还是以Python为例,比如一个整型变量,不经过转换,它就不能跟字符串做运算:
print(1 + "s")
错误
print(str(1) + "s")
正确
而对于弱类型语言,则可以执行该操作,比如javascript:
console.log(1 + "s") // 输出 1s
由于动态语言特别是弱类型动态语言的特性,数据类型可能是不安全的,所以要特别注意这种情况。这里是一个因为小小的数据类型错误而造成重大损失的案例:bilibili 2021.07.13崩溃原因。
而Python是强类型定义语言,它可以避免许多错误,但这并不意味着数据类型安全可以被忽略。
注:对于Java,1+"s"
中的+
为连接符,会自动把1
转化为字符串再做连接,Java是静态强类型语言。
不过,Python也允许你指定变量的类型,称为标注或注解。在大型程序的编写过程中,未知的变量类型可能会导致混乱,因此只要你想,就可以在变量名的后面加上:type
来指定变量类型:
a: int = 1 # 整型
b: str = "Hello World" # 字符串
但这并不能阻止变量被赋予其他类型,仅起提示的作用,当你给一个指定了类型的变量不同类型的值时,PyCharm会提醒你。当然你都指定类型了,相信不会故意赋值其他类型吧。
变量的命名
与其他大多数语言一样,变量使用字母、数字和下划线命名,数字不能放在最前面,不能和关键字(保留字)重复等。
Python的变量一般全小写,多个单词用下划线隔开,比如my_name
而不是myName
。函数名、文件名、包名也用这种命名方式,而类名则使用"大驼峰"的命名方式,比如MyClass
。
常量一般全大写,多个单词用下划线隔开,比如MY_CONSTANT
。一般我们看到全大写的变量都把它视作常量,不去修改它,但实际上它依旧是可修改的变量。
另外,如果下划线加在最前面或最后面,一般由特殊含义,特别是同时以两个下划线起始和以两个下划线结尾的,有许多是Python内置的变量或函数,比如上面提到的__name__
,命名时要注意避开这些内置变量或函数,或者索性不要使用同时两个下划线起始和结尾。
如果以下划线开头,也会让变量赋予特殊的意义,这个会在后面讲到。目前暂时不要命名以下划线开头的变量和函数。
即便没有使用到下划线,依然可能和Python内置的变量或函数命名冲突,比如max
,一旦发生这种情况一般编辑器会提示你,你可以把它改成max_value
或max_
等。
浮点数
跟其他大多数编程语言一样,浮点数在运算过程中会变得不精确:
>>> 0.1 + 0.2
0.30000000000000004
请不要直接比较两个浮点数是否相等:
>>> 0.1 + 0.2 == 0.3
False
你应该做的是判断两个浮点数相差是否小于某个范围:
>>> abs(0.1 + 0.2 - 0.3) < 1e-6
True
你也可以使用isclose()来判断:
>>> from math import isclose
>>> isclose(0.1 + 0.2, 0.3)
True
可以使用函数round()来指定精确的小数点位数:
>>> a = round(3.1415926, 2)
>>> a
3.14
如果是在字符串格式化中输出,可以这样指定小数点位数:
>>> a = 3.1415926
>>> print(f'{a:.2f}')
3.14
在Python2,整型除以整型是等于整型的,而到了Python3,整型除以整型会产生浮点型,即便得到的是整数。
>>> 6 / 2
3.0
如果需要向下取整,则用//
:
>>> 5 // 2
2
要注意的是这是标准的向下取整,所以-5 // 2
会得到-3而不是-2。如果你希望得到-2,就像绝大多数编程语言那样舍弃小数点,你可以使用int(-5 / 2)
,注意中间是/
而不是//
。
这与大多数编程语言不一样,你无需担心除数是整型导致运算结果是整型,这是一种更符合直觉的运算方式,也让Python更好的用于数学和科学计算。
其他运算
Python可以这样子在一行给多个变量赋值
a, b, c = 1, 2, 3
于是,交换两个变量的值,你可以这样操作:
a, b = 1, 2
a, b = b, a # 交换a和b的值
我们还可以用元组(后面会学到)给多个变量赋值:
a = (1, 2, 3)
x, y, z = a
可以使用单下划线分割数字来方便阅读大数字,Python会忽略数字中的下划线:
>>> a = 1_000_000_000
>>> a
1000000000
Python还有复数类型:
>>> a = 1 + 2j # 1为实部,2为虚部,注意是用j表示而不是i
>>> a
(1+2j)
>>> a.conjugate() # 求共轭复数
(1-2j)
Python提供了一个非常方便的计算幂数的语法:
>>> 2 ** 3
8
许多其他编程语言都有的运算符,Python也有,比如求余%
,位运算&
、|
、^
、<<
、>>
、~
,赋值=
、+=
、-=
、*=
、/=
、%=
、**=
、//=
、&=
、|=
、<<=
、>>=
。
赋值还有:=
,在C中有这样的写法:
int a;
printf("%d", a=1);
在第二行中,a=1
给a赋值,然后将赋值后的a传入函数,相当于:
int a;
a = 1;
printf("%d", a);
在python中,类似的操作是关键词传值:
connect(ip="123.456.78.9", port=3306, timeout=10, database="test")
而:=
就可以在表达式中给变量赋值:
>>> print(a:=1)
1
>>> a
1
但Python没有i++
这种运算符,所以如果要用,就需要使用i+=1
。
比较运算符==
、!=
、>
、<
、>=
、<=
。
身份运算符is
、is not
,它与==
和!=
的区别是,is
用于判断两个变量是否是同一个对象(对应同一个内存地址),而==
用于判断值是否相等,学过java的应该就知道==
类似于java的.equal
,而is
相当于java的==
。到后面的内容会学到如何自定义这个==
。
如果要判断一个变量是否为None
(类似于Java的null
、c语言的Null
等),应该用a is None
、a is not None
而不应该用==
。这是因为所有的None
地址都相同,而且用is
的效率比用==
要高,且运算符==
可能被重载导致结果不符合预期。
逻辑运算符and
、or
、not
。
成员运算符in
、not in
这个到后面学。
3. 流程控制
if语句
a, b = 1, 2
if a > b:
print("a更大")
elif a < b:
print("b更大")
else:
print("a、b相等")
注意else后面也有一个冒号。if语句中可以只有if,也可以只有if和else,或者只有if和一个elif。elif可以有多个。
Python中语句的范围是由缩进决定的(还记得前面的缩进的规范吗)。
a = 1
if a == 0:
print("1")
print("2")
print("3")
运行结果
3
对于a > 1 and a <= 3
可以这样写
if 1 < a <= 3:
pass # 代表什么都不做,因为语句中不能没有东西
另外,Python中的真和假首字母是大写的:
success = True
failed = False
Python没有A ? v1 : v2
这种三元运算符的写法,在很长一段时间内,人们用(A and v1) or v2
来模拟它,但这种写法存在许多问题。Python2.5之后加入了v1 if A else v2
的语句,可以用来代替这种写法。
max_num = a if a > b else b
但请注意如果条件非常复杂,还是老老实实用if语句吧,千万不要嵌套好几次这种运算符,否则代码的可阅读性将会变得很糟糕。
for语句
语法:for i in <iterator>:
,比如
for i in range(3):
print(i)
运行结果
0
1
2
其中,range()函数还可以这样用
for i in range(2, 5):
print(i)
运行结果
2
3
4
for i in range(2, 7, 2):
print(i)
运行结果
2
4
6
PEP 8要求在调用函数时有多个参数,每个逗号后参数前要加一个空格。
while语句
语法:while 条件:
i = 0
while i < 5:
print(i)
i += 1
请注意缩进,它决定了循环的范围
break、continue和else
break和continue在Python循环语句中的作用和其他语言一样,在这不再赘述。 而else语句可以在for中的iterator迭代完之后,或者while的条件为False时运行。但是,如果循环是被break结束的,则不会运行else中的语句。
for i in range(3):
print(i)
else:
print("结束")
运行结果
0
1
2
结束
match语句
注意match语句是Python3.10以后才有的。它有点像C++、java等语言的switch语句,但它可以做到的更多。由于涉及到后面的知识,而且它直到3.10以后才支持,所以可以等后面面向对象学完了或者以后需要用到的时候再回来学。Python官方文档描述了它的详细用法。
语句中的变量在语句外的访问
Python中的变量不像某些语言那样离开了它定义时所在的代码块,生命周期就会结束:
if a > b:
temp = 1
print(temp)
不过如果a > b
不成立,那么temp
将未被定义,此时第三行代码就会报错。这样子写一般编辑器会给出警告Name 'temp' can be undefined
。
这个可能导致某个名称的变量不小心被改写,导致奇怪的bug,比如下面这段代码:
i = True
... # 省略n行代码
if i:
... # 省略n行代码
for i in range(8):
pass
... # 省略n行代码
print("i是真是假:"+i)
运行结果
i是真是假:7
4. 基本数据结构
列表(List)
列表的定义和访问
下面定义了一个列表
>>> names = ['Amy', 'Peter', 'John', 'Adam', 'Martin']
PEP 8要求在定义列表时,每个逗号后元素前要有一个空格。
我们还可以使用list(range())来生成列表
nums1 = list(range(3))
nums1 = list(range(1, 5))
nums2 = list(range(2, 11, 2))
print(nums1)
print(nums2)
运行结果
[0, 1, 2]
[1, 2, 3, 4]
[2, 4, 6, 8, 10]
访问列表中的元素,下标0为第一个
>>> names[0]
'Amy'
>>> names[1]
'Peter'
如果下标为负数,则会倒着数,比如-1为最后一个元素,-2为倒数第二个。
>>> names[-1]
'Martin'
>>> names[-2]
'Adam'
使用len()函数来获取列表元素数
>>> len(names)
5
列表中可以嵌套列表:
>>> matrix = [[1, 2], [0, 1]]
>>> matrix[0][1]
2
列表看起来有点像其他语言中的数组,但它并不是数组。
增删改
使用.append()
函数给列表后面增加内容
>>> names.append('Carl')
>>> names
['Amy', 'Peter', 'John', 'Adam', 'Martin', 'Carl']
使用.insert()
来向某个位置插入元素
>>> names.insert(1, 'Jack')
>>> names
['Amy', 'Jack', 'Peter', 'John', 'Adam', 'Martin', 'Carl']
使用del
来删除列表中特定位置的元素
>>> del names[1]
>>> names
['Amy', 'Peter', 'John', 'Adam', 'Martin', 'Carl']
使用.pop()
来取出列表中最后一个元素
>>> name = names.pop()
>>> name
'Carl'
>>> names
['Amy', 'Peter', 'John', 'Adam', 'Martin']
使用.pop(index)
取出特定位置的元素
>>> name = names.pop(1)
>>> name
'Peter'
>>> names
['Amy', 'John', 'Adam', 'Martin']
使用.remove()
移除列表中某个值
>>> names.remove('Adam')
>>> names
['Amy', 'John', 'Martin']
修改列表中某个位置的值
>>> names[0] = 'Jenny'
>>> names
['Jenny', 'John', 'Martin']
列表排序
使用.reverse()
对列表中的元素排序进行反转
>>> names.reverse()
['Martin', 'John', 'Amy']
使用.sort()
对列表中的元素进行排序
>>> nums = [3, 1, 5, 4, 2]
>>> nums.sort()
>>> names
[1, 2, 3, 4, 5]
>>> nums,sort(reverse=True) # 反转排序
>>> nums
[5, 4, 3, 2, 1]
>>> words = ['d', 'b', 'bb', 'a', 'ba']
>>> words.sort() # 字符串也可以排序
>>> words
['a', 'b', 'ba', 'bb', 'd']
>>>
>>> # 如果不想改变原列表的顺序,想要产生一个新列表
>>> nums = [3, 1, 5, 4, 2]
>>> nums_sorted = sorted(nums, reverse=False)
>>> nums
[3, 1, 5, 4, 2]
>>> nums_sorted
[1, 2, 3, 4, 5]
>>>
>>> # 对于列表中元素比较复杂的情况
>>> pairs = [('A', 2), ('C', 4), ('D', 3), ('B', 1)]
>>> pairs.sort()
>>> print(pairs) # 可以看到是按字母进行排序
[('A', 2), ('B', 1), ('C', 4), ('D', 3)]
>>> # 使用lambda表达式决定怎么排序
>>> pairs.sort(key=lambda pair: pair[1]) # 按数字排序
>>> print(pairs)
[('B', 1), ('A', 2), ('C', 3), ('D', 4)]
最大、最小值和求和
>>> min(nums)
1
>>> max(nums)
5
>>> sum(nums)
15
in 和 not in
这两个运算符可以用来判断元素是否在列表中:
lst = [1, 2, 3]
if 1 in lst:
pass # do something
if 4 not in lst:
pass # do something
如果要判断一个列表是否有内容,直接这样就可以
lst = []
if lst:
print('列表中有内容')
else:
print('列表为空')
同样的它也可以用在while里:
lst = [1, 2, 3]
while lst:
print(lst.pop())
运行结果
3
2
1
在上面这段代码中,如果列表中有内容,while的条件就会是True,注意不要陷入死循环。
使用循环访问列表
lst = ['a', 'b', 'c']
for ele in lst:
print(ele)
运行结果
a
b
c
可以通过循环+下标的方式访问列表
lst = ['a', 'b', 'c']
for index in range(len(lst)):
print(ele) # 运行结果同上
使用enumerate()
可以同时使用下标和对应的元素,这是一个很常用的函数。
lst = ['a', 'b', 'c']
for index, ele in enumerate(lst):
print(str(index) + ':' + ele)
print('----')
# enumerate()还有一个参数,会让得到的index加几
for index, ele in enumerate(lst, 5):
print(f'{index}: {value}')
运行结果
0:a
1:b
2:c5:a
6:b
7:c
使用zip()
可以把两个或多个列表中相同下标的元素合在一起,如果列表的长度不同,则得到的新列表长度为最小的列表的长度。
lst1 = [1, 2, 3]
lst2 = ['a', 'b']
for ele in zip(lst1, lst2):
print(ele)
运行结果
(1, 'a')
(2, 'b')
合并两个列表
>>> lst1 + lst2
[1, 2, 3, 'a', 'b']
重复列表中的元素
>>> lst3 = lst1 * 3
>>> lst3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
使用列表推导(List Comprehensions)生成列表
如果要生成一个列表,为1到10的平方,一般的写法是
>>> arr = []
>>> for i in range(1, 10):
... arr.append(i ** 2)
...
>>> arr
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
使用列表推导可以简化代码:
>>> arr = [value ** 2 for value in range(1, 11)]
>>> arr
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
加上条件判断,比如加上if value != 5
:
>>> [value ** 2 for value in range(1, 11) if value != 5]
[1, 4, 9, 16, 36, 49, 64, 81, 100]
循环嵌套,比如为了生成 arr=(x,y), x!=y, 且x和y为1~3:
arr = []
for x in range(1, 4):
for y in range(1, 4):
if x != y:
arr.append((x, y))
使用列表推导可以写成:
>>> arr = [(x, y) for x in range(1, 4) for y in range(1, 4) if x != y]
>>> arr
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
当然,不要写太复杂的列表推导,否则对代码的可阅读性会造成灾难性的影响。
列表切片
使用lst[起始下标:终止下标]
可以获得一个新的列表,内容是旧列表起始下标到终止下标-1的元素。
>>> lst = [1, 2, 3, 4, 5]
>>> lst[1:4]
[2, 3, 4]
如果不写起始下标,则是从下标0开始,如果不写终止下标,就是直接终止到列表末端。
>>> lst = [1, 2, 3, 4, 5]
>>> lst[:3]
[1, 2, 3]
>>> lst[2:]
[3, 4, 5]
还可以使用负数下标,比如获取最后三个元素:
>>> lst = [1, 2, 3, 4, 5]
>>> lst[-3:]
[3, 4, 5]
使用列表切片可以存在超出范围的下标:
>>> lst = [1, 2, 3, 4, 5]
>>> lst[2:999]
[3, 4, 5]
>>> lst[999:]
[]
删除列表元素也可以用这种方法来同时删除多个元素
>>> lst = [1, 2, 3, 4, 5]
>>> del lst[2:]
>>> lst
[1, 2]
如果起始和终止下标都不写,则会复制整个列表。如果需要复制列表,请使用这种方法而不是直接使用=
,否则新列表和旧列表其实是同一个列表。
>>> lst1 = [1, 2, 3]
>>> lst2 = lst1
>>> del lst2[:] # 删除lst2的所有元素
>>> lst1 # lst1的元素也全被删除,因为他们俩是同一个列表
[]
>>> lst1 = [1, 2, 3]
>>> lst2 = lst1[:] # 复制列表
>>> del lst2[:]
>>> lst1 # lst1的元素都还在,因为lst1和lst2不是同一个列表
[1, 2, 3]
元组(Tuples)
元组可以理解为不可改变的列表,使用圆括号包含起来。
>>> vector = (1, 2, 3)
>>> vector
(1, 2, 3)
>>> vector[1] = 4
TypeError: 'tuple' object does not support item assignment
但你可以给vector
重新赋值,这里的不可变不是指变量是常量,而是长度和内容不可变。
如果你要创建一个只有一个元素的元组,如果这样写:s = (1)
(想想在做运算时的圆括号是用来干什么的),那么s就是整数1。所以你得这样写:s = (1,)
。
集合(Set)
集合是一个无序且元素不重复的序列,用大括号包含起来:
>>> s = {1, 2, 2, 3}
>>> s
{1, 2, 3}
我们不能通过下标和循环的方式去获取它的内容,但我们可以使用in
和not in
来判断元素是否在集合内。
我们可以使用.add()
方法给集合添加元素,使用.remove()
方法移除集合内特定元素,使用.pop()
方法移除并返回集合内随机一个元素。
使用set()
方法将列表转为集合:
>>> lst = [1, 2, 2, 3]
>>> s = set(lst)
>>> s
{1, 2, 3}
集合的运算:
>>> set1 = {1, 2, 3, 4, 5}
>>> set2 = {2, 5, 6}
>>> set1 | set2 # 取并集
{1, 2, 3, 4, 5, 6}
>>> set1 - set2 # set1中不包含set2元素的内容
{1, 3, 4}
>>> set1 & set2 # 取交集
{2, 5}
>>> set1 ^ set2 # set1和set2的元素中不在两个集合内同时存在的内容
{1, 3, 4, 6}
如果要创建空集,请使用s = set()
而不是s = {}
,因为后者是创建空字典。
字典(Dictionaries)
字典是一种包含了键值对的数据类型,用大括号包含起来:
stu = {'name': 'Peter', 'age': 18}
PEP 8要求:冒号后有空格,而冒号前无空格。
为了让字典看起来更好看一些,我们可以给它换行,注意对齐。为了方便后续添加,最后一个键值对后面可以留一个逗号。
stu = {
'name': 'Peter',
'age': 18,
}
增删改查
# 查询某个键对应的值
print(stu['name']) # 键不存在时报错
print(stu.get('name')) # 键不存在时返回None
# 判断某个键是否存在
if 'class' in stu:
pass # do something
# 增加一个键值对
stu['class'] = 3
# 修改某个键对应的值
stu['age'] = 19
# 删除某个键值对
del stu['class']
如果某个键值对不存在时,使用stu[key]
访问时会报错,使用.get()
访问时会返回None
,可以使用.get(key, default)
指定键不存在时返回的默认值。
对字典进行循环操作
# 同时获取键和对应的值
for key, value in stu.items():
print(f"{key} = {value}")
# 获取键
for key in stu.keys():
print(key)
# 获取值
for value in stu.values():
print(value)
可以对字典进行排序后再循环:
stu = {
3: "Peter",
1: "Amy",
2: "Phil",
}
for i in sorted(stu):
print(f"{stu[i]}'s id is {i}")
运行结果
Amy's id is 1
Phil's id is 2
Peter's id is 3
嵌套(Nesting)
以上的数据类型都可以嵌套,比如:
lst = [{"key": "value"}, {"key": "value"}]
s = {"key": "value", "list": [1, 2, 3]}
students = {
"Linda": {
"age": 18,
"language": ["Chinese", "English"]
},
"Peter": {
"age": 19,
"language": ["English", "French"]
}
}
5. 函数与模块
函数的定义
函数使用def 函数名(参数):
来定义,使用函数名(参数)
来调用,比如:
# 定义函数
def print_hello_world():
print('Hello World!')
def get_max(a, b):
return a if a > b else b
# 调用函数
print_hello_world()
max_num = get_max(1, 2)
函数需要先被定义才能生效,因此函数的调用要写在函数之后,否则会提示“函数未定义”。
注意函数名不要跟已有的变量名冲突,变量名也不要和已有的函数名冲突。
PEP 8要求(非类里的)函数与函数间、函数与其他代码间要间隔两行,比如上面的代码。
对于没有返回值的函数,你可以认为它返回None
,比如:
>>> a = print('123')
123
>>> print(a)
None
因此我们可以这样做:
>>> print('yes') if a > b else print('no')
函数的文档
在函数体内首行开始使用多行注释编写文档,用来说明该函数的作用,使用方法等。
文档的第一行应简明说明函数的简短摘要,最好只用一句话说明白。如果文档不止一行,应在第一行说明摘要后空一行再继续写。
def send_message(user, mes):
"""
向用户发送消息。
参数:
user: 向哪个发送消息
mes: 消息的内容
"""
...
通过sendmessage.__doc__
即可获取该函数的文档。
函数参数的默认值
定义函数时,可以指定参数的默认值,这样在调用函数时就可以省略一部分参数。有默认值的参数必须放在无默认值参数的后面。
def send_message(user, mes, color='red'):
...
send_message(user, 'Hello')
send_message(user, 'World', 'green')
警告:如果默认值是可变类型,比如列表、字典、类对象,则会产生累积作用,如下:
def func(a, lst=[]):
lst.append(a)
print(lst)
func(1)
func(2)
func(3)
运行结果
[1]
[1, 2]
[1, 2, 3]
如果你想避免这种情况,可以这样写:
def func(a, lst=None):
if lst is None:
lst = []
lst.append(a)
在PyCharm中,写成前者会提示你存在这种情况,并可以一键修改。
在传递列表等可变类型参数时,列表可能被函数修改,比如:
def func(lst: list):
print(lst)
lst.append('a')
l = [1, 2, 3]
func(l)
print(l)
运行结果
[1, 2, 3]
[1, 2, 3, 'a']
如果想避免这种情况,可以传入一个复制的列表:
l = [1, 2, 3]
func(l[:])
返回多个数据
Python的函数可以同时返回多个数据:
def get_location():
return 1, 2, 3
x, y, z = get_location()
如果你只想获得其中一部分数据,比如上面这行代码你只对y
感兴趣,你可以把另外几个数据换成_
:
_, y, _ = get_location()
函数参数的注解
如果你的函数参数、返回值的类型是非常确定的,你可以给他们加上注解作为提示。
def func(a: int, b: str = "default value") -> str:
...
这段代码表明参数a
应传入整型,参数b
应传入字符串类型,而且它的默认值为"default value"
,函数的返回值是字符串类型。
但实际上,这并不能阻止用户传入其他类型。不过如果这样做,编辑器一般会给出警告提示用户传错参数了。
你也可以使用字符串作为注解:
def func(a: int, b: 'int>0', c: 'do not pass a int here') -> 'Nothing will be returned':
...
关键词参数
对于这样的一个函数:
def send_message(title, text, color='red', size='32px'):
...
除了按位置传递参数,你也可以使用关键词来传递参数,这样关键词部分就可以随意调整位置了。比如:
send_message(title='Here', text='Hello', size='16px')
send_message('Here', size='16px', color='green', text='Hello')
但关键词部分全部都要放后面,不能关键词传参后面又带一个位置传参,比如这样写是错的:
sendmessage(title='Here', 'Hello') # 错误写法
*name和**name参数
如果函数形参中有这两种参数,他们要放在参数的最右边,且*name
要放在**name
的前面。*name
用于接收形参列表之外的位置参数并形成一个元组(Tuple),比如:
def send_message(user, *messages):
...
调用该函数:
send_message(user, 'Hello', 'World', 'Hi')
**name
接收形参列表之外的关键词参数并形成一个字典(Dictionary),比如:
def send_message(user, *messages, **datas):
...
调用该函数:
send_message(user, 'Hello', 'World', color='red', size='32px')
仅位置参数和仅关键词参数
这个特性在Python3.9及以后才支持。
在函数形参中使用/
和*
标记分隔,可确定仅位置参数和仅关键词参数。
格式:def 函数名(仅位置参数, /, 一般参数, *, 仅关键词参数)
。
比如,一个函数必须全部按位置传参,你可以这样写:
def func(a, b, c, /):
...
调用该函数时,只能使用func(a, b, c)
而不能使用关键词。
一个函数必须全部使用关键词:
def func(*, a, b, c):
...
调用该函数时,只能使用func(a=xxx, b=xxx, c=xxx)
的形式。
对于
def func(a, b, /, c, d, *, e, f):
...
则a
和b
必须按位置传参,c
和d
既可以按位置也可以按关键词传参,e
和f
必须按关键词传参。
函数的位置
函数还可以定义在函数内,比如:
def funcA():
a = 2
def funcB():
print(a * 2)
funcB()
可以看到,函数funcA
内部又定义了一个函数funcB
,而且funcB
可以访问外部变量a
,但不能修改它。由于funcB
在funcA
的内部,因此在funcA
的外部是不能调用funcB
的。
除此之外,在if
、while
等语句内都可以定义函数,那么你就可以做出这样的骚操作:
import platform
if platform.system() == 'Windows':
def func():
print('a')
else:
def func():
print('b')
func()
如果运行这段代码的操作系统是Windows系统,则执行func()打印的结果是a
,否则则是b
。
将函数作为变量
函数也是可以赋给变量的,如下:
def func(a, b):
return a + b
f = func
print(f(1, 2)) # 输出3
比如说,你想添加一个监听器,当用户点击按钮时执行一段命令,此时就可以:
def on_click():
...
button.add_click_listener(on_click)
当然,不一定要把函数写在外头:
def init_listeners():
def on_click():
...
button.add_click_listener(on_click)
匿名函数:lambda表达式
如果上面那个例子你不想单独写一个函数,而且点击事件非常简单(比如就只有一个print()
),可以使用匿名函数。
button.add_click_listener(lambda: print('Clicked'))
你也可以用它创建简单函数:
f = lambda x, y: x + y
print(f(1, 2))# 输出3
或更直接,创建一个匿名函数并运行:
>>> (lambda x, y: x + y)(1, 2)
3
这适用于使用次数比较少、无需复用的简单函数,不过同样的,用它是为了让代码更加优雅,如果函数比较复杂,使用lambda表达式会影响可阅读性,就不建议使用这种方法。
变量的作用域
在函数中,可以读取函数外的变量,但不能修改它。
a = 1
def func():
b = a + 1
如果我们希望外部变量是一个全局变量,在函数中也能被修改,该怎么做呢?
答案是在修改前加一个global 变量名
a = 1
def func():
global a
a = 2
如果在函数内定义一个函数,且需要修改外函数内的一个变量,则是使用nonlocal
:
a = 1
def funcA():
global a
a = 2
b = 1
def funcB():
nonlocal b
b = 2
多文件项目
当程序变得复杂时,我们便需要将代码写到多个文件中以便管理。
模块的导入
比如创建一个文件my_module.py
并编写代码:
# my_module.py
print('This is my module')
然后在main.py
中导入它:
# main.py
import my_module
运行main.py
,就可以看到输出
This is my module
我们也可以把函数写到my_module.py
里面:
# my_module.py
print('This is my module')
def func(mes):
print(mes)
然后在main.py
中调用它:
# main.py
import my_module
my_module.func('Hello')
运行结果
This is my module
Hello
这里可以看到,当我们导入模块时,也会运行模块中的代码。
在Python中有一个叫this的模块,导入它就会打印一段信息,这段信息被称为"The Zen of Python"(Python之禅),它总结了Python的风格,感兴趣的可以上网查询。
import this
if __name__ == '__main__'
如果希望别人导入我的my_module
时不运行不必要的代码,该怎么做呢?
还记得最前面提到的这段代码吗?
if __name__ == '__main__':
...
这个__name__
是什么呢?我们创建my_project.py
和my_module.py
两个项目,分别打印出__name__
的值,并在my_project
中导入my_module
:
# my_project.py
import my_module
# my_module.py
print(__name__)
运行my_module
,输出:
_main_
运行my_project
,输出
my_module
在运行my_module
时,解释器会把'__main__'
赋值给__name__
,而在my_project
中导入my_module
时,my_module
中的__name__
就是它的名称。我们可以通过my_module.__name__
来获取模块的名称。
为了让自己的模块被导入时不会运行到不必要的代码,我们加上这个if来判断运行的是不是这个模块。
导入模块的其他操作
导入模块时,可以给这个模块起别名:
import my_module as mm
mm.func()
可以单独导入模块的某些内容,这样可以不用加上模块前缀my_module.
即可调用函数,同样可以给导入的函数起别名
from my_module import func1, func2, func3 as f3, func4 as f4
func1() # 不需要 my_module.func1()
f3()
从模块导入所有内容,不建议使用,因为这样非常容易发生命名冲突:
from my_module import *
导入第三方模块
很多时候我们需要用到第三方模块,要怎么导入这些模块呢?
这里以PyCharm为例。使用PyCharm创建项目时,默认情况下会在这个项目下创建一个虚拟环境venv
,这样可以让不同的项目使用不同的Python版本和不同的库。你也可以在设置里调整项目使用的虚拟环境,比如调到你系统安装的环境中。但是可能会产生这样的问题,比如项目A使用了1.0.0的X库,项目B使用了2.0.0的X库,这两个不同版本的库是不兼容的,而一个虚拟环境不能同时安装两个不同版本的同一个库,因此需要分出多个虚拟环境。
为了给你的项目添加库而不是给系统环境添加库,你可以在PyCharm中点击左上角的File(文件)-Settings(设置)-Project(项目):你的项目名-Python Interpreter(Python解释器),将解释器调为你项目中的虚拟环境并点击+号,即可添加第三方模块。
由于Python仓库服务器在国外,下载速度极慢甚至容易失败,建议使用国内镜像源。
- 在上文提到的那个位置点击+号后,有一个Manage Repositories的按钮,点击它即可添加源。
- 如果1中找不到那个按钮,则可以关闭设置页码,然后在编辑器的下方找到Python Packages(Python包)并点击它,然后在弹出的标签中点击搜索栏后的齿轮按钮,即可添加源。
常用源:
清华:https://pypi.tuna.tsinghua.edu.cn/simple/
阿里云:http://mirrors.aliyun.com/pypi/simple/
豆瓣:http://pypi.douban.com/simple/
中国科技大学:https://pypi.mirrors.ustc.edu.cn/simple/
华中理工大学:http://pypi.hustunique.com/
山东理工大学:http://pypi.sdutlinux.org/
如何使用命令添加第三方模块呢?
当然是使用pip
命令。如果没有安装pip
,也可以用python -m pip
。
- 安装某个模块:
pip install xxx
- 安装某个模块的某个版本:
pip install "xxx==1.0.0"
- 使用豆瓣镜像源安装:
pip install -i http://pypi.douban.com/simple xxx --trusted-host pypi.douban.com
- 升级某个模块到最新版本:
pip install --upgrade xxx
- 查看某个模块的具体信息:
pip show xxx
- 查看当前环境安装了哪些模块:
pip list
- 卸载某个模块:
pip unistall xxx
当你把你的项目交给用户使用时,你需要让用户也安装项目依赖的模块,但你的项目可能会有非常多的依赖,而且还需要是指定版本以防止不兼容,让用户自己一个一个去安装不太现实,此时你就可以使用:
pip freeze > requirement.txt
它会生成一个文件requirement.txt
,里面说明了需要的模块和指定的版本。将这个文件跟项目一并打包然后发送给用户。
用户只需pip install -r requirement.txt
即可安装需要的所有模块
dir()函数
dir()
是一个内置函数,它可以得到模块定义的名称,是一个排序过的列表:
>>> a = 1
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__',
'__spec__', 'a']
同样你也可以用它获取某个模块内定义的名称:
>>> import sys
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', ...此处省略...]
包
Python中可以在项目所在目录下创建子目录作为包,然后在子目录下创建新的.py
文件。
比如,在项目根目录下创建一个目录effects
,然后在这个子目录下再创建一个目录managers
,然后再在这个子目录下创建一个文件my_module.py
。
导入和使用这个模块的方法是
import effects.managers.my_module
# 调用my_module模块里的函数func()
effects.managers.my_module.func()
可以发现,使用导入的模块需要加上很长的一段前缀,如果不想加上这些前缀,可以这样写:
from effects.managers import my_module
在Python 3.2之前,需要在包下创建__init__.py
文件来告诉Python这是个包。PyCharm在创建包时,也会自动创建这个文件。最简单的情况下,这个文件里面啥都不用写。这是一个目录结构:
project/
├ main.py
└ effects/
├ __init__.py
└ managers/
├ __init__.py
└ my_module.py
└ my_module2.py
在导入包下的模块时,会先运行__init__.py
中的代码,你可以在这里做一些初始化的操作等。
在导入包内的所有模块时,你会发现直接这样写并不会导入任何模块:
from effects.managers import *
my_module.func() # 报错,找不到my_module
你需要在managers
下的__init__.py
中添加
__all__ = ['my_module', 'my_module2']
这样上面报错的代码就不会报错了。不过在生产环境中,不建议使用import *
。
学到这里,你已经可以使用Python写比较复杂的程序了。接下来将会介绍Python的面向对象以及更高级的内容,你可以点击这里到达下一页进行阅读。
- 1 2