To boldly go where no one has gone before.

【退役生活】Python 学习笔记

2020-02-03 16:22:52


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 了吧。