Python 笔记
基本语法
结构
Python 程序不像 C++ 中需要引入头文件,可以直接开始写语句。
下面是喜闻乐见的 Hello World 程序:
print('hello world!')
没错,就是这么简单。
语句
Python 使用缩进与换行(和 C++ 中的 ;
和 {}
不同)来判断语句间的逻辑关系。
用 \
加在行末,表示本行与下一行为一条语句。
也可以在一行写多条语句,用分号隔开。
Python 中,若一条语句后要接整个程序块,则需要在该语句后打 :
,类似于 C++ 中的 {}
。例如:
if a == b:
print('yes')
print(a, b)
如果要用中文字库,则需要引入 utf-8
编码,在程序文件开头加入:
# coding=utf-8
# 注意 "=" 两侧不能有空格
或者
# -*- coding: UTF-8 -*-
输出
Python 标准输出使用 print()
函数。例如:
print(1)
print()
会在输出的内容之后自动使用结束符 end
(默认为换行符);若有多个输出对象,则自动插入分隔符 sep
(默认为空格)。两者均可自定义,例如:
print(1, 2, 3, sep = '<', end = ' ')
print('hello', end = '!')
输出为:
1<2<3 hello!
变量
Python 中的变量赋值不需要类型声明,在赋值时自动为变量设定类型。
不过 Python 变量的存储方式非常奇葩,例如:
a, b = 1, 1
if id(a) == id(b):
print('yes')
else:
print('no')
如果按照 C++ 的思维去想,会认为这里应该输出 no
;然而如果在 IDE 里跑一下,会发现程序输出了 yes
。实际上,Python 中的赋值类似于 指针。在上面的例子中,对 a, b
赋值的时候,其实是把指向 1 的 地址 赋给了 a, b
,所以造成了这样的魔幻结果。
引号
三引号('''
或 """
)可以用来表示多行字符串,例如:
str = '''I am your father
nooooooooooo
'''
注释
采用 #
开头,例如:
# Hello
多行注释则用三引号开头。
可以对多个对象赋值,例如:
a, b, c = 1, 2, 3
特色运算符
运算符 | 描述 |
---|---|
** |
幂运算 |
// |
下取整除法 |
in |
如果在指定的序列中找到对应值返回 True ,否则返回 False |
not in |
与 in 相反 |
is |
判断两个标识符是不是引用自一个对象,x is y 等价于 id(x) == id(y) |
not is |
与 is 相反 |
其余运算符与 C++ 类似。
数据类型
Python 中,所有数据类型都是 对象,而这些变量又分为 可更改对象 和 不可更改对象。修改不可更改对象是不被编译器接受的。
数字
共有 int, float, complex
三种数字数据类型(Python3 取消了 long
),存储空间几乎无限(Python3 中 int
/ Python2 中 long
自带高精,永远不会爆)。
在上文中已经提到,Python 对变量赋值的方式类似于指针,所以数字可以认为是不可更改对象。
字符串
访问字符串中的单个字符用 []
实现,也可以用 s[HEAD:TAIL]
来截取子串,范围左闭右开。如果再附上一个参数,即 s[HEAD:TAIL:STEP]
,表示截取步长为 STEP
(默认为 1)。
下标的访问规则比较奇葩,用一个例子表示:
字符串 | a | b | c | d |
---|---|---|---|---|
正序编号 | 0 | 1 | 2 | 3 |
倒序编号 | -4 | -3 | -2 | -1 |
对于字符串 s = 'abc'
:
操作 | 结果 |
---|---|
str |
abc |
str[0] |
a |
str[0:2] |
ab |
str[2:] |
bc |
str * 2 |
abcabc |
str + 'def' |
abcdef |
格式化
类似于 C++ 中的 printf()
函数,Python 中的 print
也支持格式化输出,例如:
print('%s %d' % ('hello', 1))
其中,格式化符号和 C++ 类似。
如果在字符串引号前加 r
(例如 r'abc\n'
),则不会翻译 \n
之类的转义符,而按照原始字符串内容输出。
程序结构
条件语句
if a > b:
print(a)
else:
print(b)
程序结构
while
循环
while a == 1:
print(b)
for
循环
格式为:
for var in sequence:
# do something
也就是说需要一个序列 sequence
用于储存 var
的所有值,例如:
for char in 'abc':
print(char)
# 也等价于:
str = 'abc'
for i in range(len(str)):
print(str[i])
如果要实现迭代数字,则可以使用 range()
函数。
range(LEFT, RIGHT[, STEP])
表示生成一个包含 [LEFT, RIGHT)
区间(可选步长为 STEP
,默认为 1)所有数的序列。
语句 | 描述 |
---|---|
range(5) |
0,1,2,3,4 |
range(2,5) |
2,3,4 |
range(0,5,2) |
0,2,4 |
range(4,-1,-1) |
4,3,2,1,0 |
当然,Python 里的 for
无法实现像 C++ 一样的高级功能。
pass
语句
什么也不做。
数据结构
列表
列表类似于数组,支持修改、查询。同一个列表中的元素可以是不同类型。创建列表时使用中括号 []
。
list = ['a', 'b', 'c', 1, 2, 3]
这样就创建了一个 list
列表。
列表下标的访问规则和字符串是一致的。
修改
列表支持直接用等号修改内部元素,比如 list[0] = 'a'
。
list.append(object)
函数可以在列表末尾插入一个新元素,例如:
list = [1, 2, 3]
list.append(4)
del
语句可以删除列表中的元素,例如:
list = [1, 2, 3]
del list[0]
则 list
就变为了 [2, 3]
。
list.insert(index, object)
函数可以把一个元素插入到列表中的指定位置,例如:
list = [1, 2, 3]
list.insert(1, 'good')
则 list
就变为了 [1, 'good', 2, 3]
。
列表也支持 + 和 * 操作,其意义与字符串相同。
栈
列表可以作为栈使用,不需要额外引入模块,其功能如下:
函数名 | 描述 |
---|---|
stack.append(element) |
在栈顶加入元素 |
stack.pop() |
弹出且返回栈顶元素 |
队列
列表也可以作为队列使用,但需要引入一个模块,创建一个队列的方法如下:
from collections import deque
queue = deque([1, 2, 3, 4])
其功能如下:
函数名 | 描述 |
---|---|
queue.append(element) |
在队尾加入元素 |
queue.pop() |
弹出且返回队尾元素 |
queue.popleft() |
弹出且返回队首元素 |
例如:
from collections import deque
queue = deque([1, 2, 3, 4])
print(queue.popleft())
print(queue.pop())
输出为:
1
4
列表推导式
列表推导式可以用于简便地创建列表。格式为:
[expression for...[if...][for...[if...]...]]
例如:
list = [x for x in range(1, 5)]
则 list = [1, 2, 3, 4]
。另外,if
可以用于过滤元素,例如:
list = [x for x in range(1, 5) if x != 2]
则 list = [1, 3, 4]
。
推导式语句也可以嵌套,每一个 for - if
算作一层,其顺序与嵌套循环相同。例如:
list = [x * y for x in range(1, 4) if x != 2 \
for y in range(3, 6)]
则 list = [3, 4, 5, 9, 12, 15]
。
元组
元组与列表类似,但区别在于元组不支持元素的修改。创建元组时使用小括号 ()
。
注意: 若元组内只有一个元素,元素后也需要打逗号,例如 tup = (1, )
。这不同于列表和字典。
元组不支持内部元素的修改、删除,但支持整体删除,比如 del tup
。
运算符与列表一致。
集合
集合与数学上的集合类似,是一个无序的不重复元素序列,支持集合间的运算。创建时使用大括号 {}
;特殊地,如果创建一个空集合,必须使用 set = set()
这种方式,因为空大括号 {}
创建的是空字典。
修改
set.add(element)
函数可以将元素加入集合。当然,由于集合的性质,此过程将自动去重。
set.update()
可以把括号中容器内的所有元素(把容器拆掉)加入集合中。此处的括号中 不能有单个元素。例如:
set = {1, 2, 3}
set.update([4, 5], {6, 7}, (8, 9))
print(set)
则会得到输出 {1, 2, ..., 9}
。
set.remove(element)
函数可以将元素从集合中删除,但是如果元素不存在,会引发错误。如果希望不发生错误,则可以使用 set.discard(element)
函数。
set.clear()
可以清空集合。
运算
集合支持数学上的集合运算。
运算符 | 意义 |
---|---|
A & B |
$A\cup B$ |
A | B |
$A\cap B$ |
A - B |
$A\setminus B$,$A$中不出现在$B$中的元素 |
A ^ B |
$\complement_{A\cup B}A\cap B$,不同时在$A,B$中出现的元素 |
字典
字典可以理解为映射(C++ 中的 map<>
容器),用于储存键值对,也支持对键值对的修改。字典对值没有规定,可以是任意数据类型;但 键必须不可变,也就是说 列表 和自定义数据结构是 不能作为键 的。创建时使用大括号 {}
,键和值之间用冒号 :
隔开,例如:
dict = {'a' : 1, 'b' : 2, 'c' : 3}
访问键对应的值时,把键放在中括号中,将返回对应的值。例如:
dict = {'a' : 1, 'b' : 2, 'c' : 3}
print(dict['b'])
则会得到结果 2
。
如果需要遍历字典,则可利用 dict.items()
获得字典内所有的键值对,例如:
for key, val in dict.items():
print(key, val, sep = '->')
初始化
dict.fromkeys(seq[, val])
可以创建一个新字典,以序列 seq
中元素作为字典的键,val
为字典所有键对应的初始值。
修改
与列表类似,字典也支持直接用等号修改键所对应的值(但键必须是存在的)。
del
语句可以删除单独的一组键值对,例如 del dict['a']
将删除字典中以 'a'
作为键的键值对;也可以删除整个字典,例如 del dict
。
dict.clear()
函数可以清空字典,但不会删除字典。
迭代器
迭代器是访问集合元素的一种方式。和 C++ 相同,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
创建迭代器
格式如下:
list = [1, 2, 3, 4]
it = iter(list)
则对 list
创建了迭代器 it
。
迭代器遍历
使迭代器位置前进可以用 next(it)
函数,其返回当前迭代器后,自动将迭代器移向下一个位置。
若用此种方式遍历,则要使用异常处理:
list = [1, 2, 3, 4]
it = iter(list)
while True:
try:
print(next(it))
except StopIteration:
break
# 也可以写为 sys.exit(),需引入 sys 模块
除此之外,迭代器也支持 for
遍历,例如:
list = [1, 2, 3, 4]
it = iter(list)
for x in it:
print(x)
自定义迭代器
类可以自定义迭代器,只需要重构 __iter__()
和 __next__()
函数,分别用于构建迭代器和进行迭代。其中,为了防止出现死循环的情况,需要在 __next__()
中定义边界,到达边界后触发 StopIteration
异常,即可被 try
捕捉到。
class array:
def __iter__(self):
self.num = 1
return self
def __next__(self):
pre = self.num
if self.num > 5:
raise StopIteration
self.num += 1
return pre
arr = array()
it = iter(arr)
while True:
try:
print(next(it))
except StopIteration:
break
函数
函数以 def
开头,例如:
def printstr(str):
print(str)
return
其中,return
是可选的,其后面也可以跟一个变量作为函数的返回值,即使没有写 return
编译器也会自动按照缩进来判断函数是否应该结束;def
可以在任何地方开始。
参数的修改
传入的参数为可修改变量,则直接修改外部对象,类似于引用;而传入的参数为不可修改变量,则修改只影响函数内变量的值。例如:
def write(a):
a = 10
print('in function:', a)
a = 5
print('out of function:', a)
write(a)
由于数字是不可修改对象,输出为:
out of function: 5
in function: 10
这和上文提到的 Python 变量存值机制有关,数字型变量本身就只是个指针,对其进行修改并不能修改到指向的数字。
关键字参数
为了防止搞乱顺序,Python 支持关键字参数,例如:
def write(a, b):
print('a =', a, 'b =', b)
write(b = 2, a = 1)
输出为:
a = 1, b = 2
有点似曾相识的感觉… 似乎 C++ 的构造函数就有这种写法。
默认参数
默认参数可以在没有传入参数的情况下对参数自动赋值,例如:
def write(a, b = 2):
print('a =', a, 'b =', b)
write(1)
输出为:
a = 1, b = 2
不定长参数
前加星号 *
的参数长度可以不定。例如:
def write(*array):
for a in array:
print(a)
return
write(1)
write(2, 3, 4)
输出为:
1
2
3
4
匿名函数
lambda
用于声明 匿名函数。lambda
函数只是一个表达式,而非代码块,所以能表达的逻辑非常有限。lambda
函数还拥有 自己的命名空间,不能访问自有参数列表之外或全局命名空间里的参数,这也是“匿名”的由来。格式为:
func_name = lambda arg1[, arg2[, ...[, argn]]] : expression
例如:
sum = lambda arg1, arg2 : arg1 + arg2
print(sum(1, 2))
输出为 3
。
内置函数
cmp(x, y)
情况 | 返回值 |
---|---|
x < y |
-1 |
x = y |
0 |
x > y |
1 |
map()
格式为:
map(function, iterable, ...)
对于列表中的所有元素调用函数。最简单的例子是读入同一行的数字,例如:
a, b = map(int, input().split())
等价于将 input()
获得的字符串拆分以后,对每个部分使用一次 int()
函数转换成整型变量。又例如:
模块
模块用 import
导入。导入模块后,调用模块内的函数时的格式应该为:
mod_name.func_name(...)
。例如:
import math
a, b = map(int, input().split())
print(math.gcd(a, b))
这个程序可以实现计算最大公约数。
除此之外, from
语句可以从模块中导入一个指定的部分到当前命名空间中,格式为:
from modName import funcName
还可以将模块内所有函数导入,即:
from modName import *
面向对象
创建类
格式如下:
class className:
'description'
classSuit # 实体
一个简单的例子:
class student:
'class for student'
count = 0
def __init__(self, studentName, studentId):
self.studentName = studentName
self.studentId = studentId
student.count += 1 # 此处修改的是全体类的属性
def display(self):
print('name:', self.studentName, 'id:', self.studentId)
def displayCount(self):
print('count:', self.count)
std1 = student('max', 28)
std2 = student('alice', 22)
std1.display()
std2.display()
print('tot:', student.count)
print(student.__doc__)
注意到,每个类的函数参数中都有一个 self
,它代表函数要施加于的 实例 而非类。也就是说,所有类的函数都 必须带有 self
参数,但是在实例调用函数的时候,符号 .
即自动引入了实例作为参数,因此括号中不用再写一遍实例名作为参数。
管理属性
和 C++ 中为每一个实例声明一块专用空间不同, Python 中实例的属性不是被保存在连续的一段内存空间中,而是 动态 的,这决定了 Python 中的类支持 动态修改属性。
函数名 | 描述 |
---|---|
getattr(obj, name[, default]) |
访问对象的属性 |
hasattr(obj,name) |
检查是否存在一个属性 |
setattr(obj,name,value) |
设置一个属性;如果属性不存在,会创建一个新属性 |
delattr(obj, name) |
删除属性 |
而对于所有的类,它们都有共同的一些内置类属性。
属性 | 描述 |
---|---|
__dict__ |
返回所有属性构成的字典 |
__doc__ |
返回描述字符串 |
__name__ |
返回类的名字 |
__module__ |
返回类所在的模块 |
__bases__ |
返回类的所有父亲构成的元组 |
销毁
__del__()
函数在对象销毁时自动调用。Python 利用计数器来进行销毁:若一个对象被引用的次数为 0,或者产生循环引用(循环垃圾),则将对象销毁并回收空间。与 __init__()
相同,__del__()
也是支持自定义的。
继承
类的继承简化了代码重利用的过程。其格式如下:
class className(parentName1):
classSuit
子类会自动继承父类的所有函数,但子类也可以自定义自己的函数,这被称作方法重写。子类自己的函数定义优先于父类函数定义,也就是说编译器会优先在子类查找函数定义,如果没有再回到父类查找。
当然,也可以继承多个类,格式为:
class c(p1[, p2, [...pn]]):
classSuit
如果子类没有函数定义,在父类中查找相关函数定义的顺序为 从左到右。
重载运算符
例如重载向量的加法运算符,且实现比较模长大小:
class vector:
x, y = 0, 0
def __init__(self, x = 0, y = 0):
self.x = x
self.y = y
def __add__(self, other):
return vector(self.x + other.x, self.y + other.y)
def __lt__(self, rhs): # 重载小于号
import math
m1 = math.sqrt(self.x ** 2 + self.y ** 2)
m2 = math.sqrt(rhs.x ** 2 + rhs.y ** 2)
if m1 < m2:
return True
else:
return False
def display(self):
print('vector(%d, %d)' % (self.x, self.y))
v1 = vector(-2, 0)
v2 = vector(4, 5)
v3 = v1 + v2
v3.display()
if v1 < v2:
print('|v1| < |v2|')
运算符的函数有很多,需要时可以上网查一下。
私有属性
以双下划线开头的属性,例如 __attr
,是不能在类的外部调用的,类似于 C++ 中的 private
属性。不过如果实在要调用,可以使用 object._className__attrName
的格式进行访问。
保护属性
以单下划线开头的属性,例如 _attr
,表示 protected
类型的属性,只能用于本身和子类的访问,不能用于 from module import *
。
异常处理
异常捕捉
try...except...
语句可以实现 异常捕捉。执行时,会默认执行 try
子句;若发生异常,则中断当前子句,在 except
子句寻找对应的异常;匹配异常后执行对应的 except
子句;若无法匹配,则将异常呈递给 try
子句。异常的类通常在 sys
模块中。
一个 except
子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:
except (RuntimeError, TypeError, NameError):
pass
最后一个 except
子句可以忽略异常的名称,类似 C++ 里 switch()
语句的 default
情况。
except
子句后可以接一个 else
子句,表示如果没有错误发生应该执行的代码;后面还可以接一个 finally
表示无论何种情况都要执行的语句。若发生异常,则会先执行完 finally
子句,再进入 except
。整个框架为:
try:
# do something
except Error1:
# handle with error1
except Error2:
# handle with error2
except:
# default error
else:
# when no error occurred
finally:
# do cleaning
抛出异常
raise
语句可以用于抛出异常,raise
唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception
的子类)。其格式为:
raise [Exception [, args [, traceback]]]
例如:
raise Exception('shit!')
运行后会得到:
Traceback (most recent call last):
File "d:\Markdown\笔记\Python\test.py", line 1, in <module>
raise Exception('shit!')
Exception: shit!
自定义异常
Python 支持创建自定义的异常类。例如:
# -*- coding: UTF-8 -*-
class myError(Exception):
def __init__(self, val):
self.val = val
def __str__(self): # 返回描述信息
return repr(self.val)
# repr() 转化为供解释器阅读的形式
raise myError('shit!')
注意,异常类 必须是 Exception
的子类。
作用域与命名空间
Python 的作用域和命名空间非常奇葩。global
关键字用于声明全局变量,nonlocal
关键字用于声明外部作用域。例如:
a = 1
def func():
a = a + 1
print(a)
func()
它是会报错的,因为 a
是局部变量;也就是说,这里的 a
并不是 a = 1
那个 a
,而是函数内部的另一个变量。( ???)
实际上,此处必须把 a
声明为全局变量,才能对其进行修改。
a = 1
def func():
global a
a = a + 1
print(a)
func()
而下面这个例子则允许了作用域嵌套关系的修改:
def func1():
a = 10
def func2():
nonlocal a
a = 100
print(a)
func2()
print(a)
func1()
nonlocal
声明使得 func2()
内的 a
被识别为 func1()
中的 a
,所以 func2()
有能力修改这个变量的值,因此输出为:
100
100
而如果删去 nonlocal
,输出就变为了:
100
10
不知道谁设计出这么难的一套命名空间系统…
至此,Python 的简单语法记录完毕。C++ 能写的程序,应该都能翻译为 Python 了吧。