Python学习笔记 —— 教材的笔记
教材: Introduction to Computation and Programing Using Python
作者:John V. Guttag
edx课程:https://courses.edx.org/courses/course-v1:MITx+6.00.1x+2T2017_2/course/
Turing completeness 图灵完备性
- Church-Turing thesis
这是一项计算机理论基础,它定义了一问题是否是可计算的,即程序代码能实现的功能是有限的,不是所有问题都是可计算的。
图灵证明了停止问题(Halting Problem)是不可计算的。停止问题是指判断一段程序是会一直执行下去,还是会在某个时间后停止。
- Universal Turing Machine
通用图灵机是一种能解决所有可计算问题的简陋计算机,虽然仅能利用纸带进行基本操作。
- Turing completeness
图灵完备性是指编程语言是否有能力描述所有可计算的问题。
Syntax, Static semantics, Semantics
- Syntax 语法
语法决定了一段代码是否是在语法是合理的,即是否满足基本的语法规则,若出现语法错误,python解释器会在执行前检查语法错误。例如,‘x = ’,‘x y’,‘x = y + ’,‘x = ( x ‘。
- Static semantics 静态语义
语法上正确的代码不一定有明确的意义,比如12/’abc’。语法检查过程不会发现这些错误,而Python对执行前的静态语义检查,没被检查出来的静态语义错误将在执行过程中报错。
weak static semantic checking 是python的一大特点。
- Semantics 语义
语法上和静态语义上都正确的代码若能正确的反应程序员想达到的目的,那么这段代码在语义上也是正确的。若代码执行解决和程序员期望不一致,那么这段代码就有语义错误。
解释型语言和编译型语言
- 解释型(Python, Matlab)
运行速度慢、占空间,而且代码不能加密。如果要发布你的Python程序,实际上就是发布源代码。优点是便于debug。
- 编译型(C)
运行快、占空间少。
标量和非标量(Scalar and None-Scalar)
标量不可分没有内部结构。
非标量可分且有内部结构。
Indentation 缩进
Python 使用缩进的方式决定一行代码属于哪个代码块或是哪个程序体。下面程序要是进行任何缩进将会形成语法错误。
x = 8
if x % 2 == 0:
print('Even')
else:
print('Odd')
print('Done with conditional')
Even
Done with conditional
+ * 的重载
'abc' + 'efg'
'abcefg'
'abc' * 8
'abcabcabcabcabcabcabcabc'
[1,2,3]*3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
(1,2,3)*3
(1, 2, 3, 1, 2, 3, 1, 2, 3)
切片
s[start:end]
返回一个从s的第start个元素开始到第end - 1个元素的切片。
list的切片还是list,tuple的切片是tuple,str的切片还是str。
取整与四舍五入
int()函数使用的去尾取整,而四舍五入需要使用round()
int(3.9)
3
round(3.6)
4
round(3.4)
3
import math
round(math.pi,2)
3.14
二分查找
二分查找是解决很多查找问题的利器,但二分查找需要查找空间是有序的。
浮点数不等于实数
二进制能表示所有的十进制整数,但不能表示所有的有理数。
不建议使用浮点数 == 浮点数的比较。
x = 0.0
for i in range(10):
x = x + 0.1
if x == 1.0:
print(x, '= 1.0')
else:
print(x, 'is not 1.0')
0.9999999999999999 is not 1.0
牛顿-拉普森 Newton-Raphson ——解多项式方程
- 牛顿大大厉害
p(x) = 0的解可以通过g = g - p(g)/p’(g)得到。g最开始可以是任意值,通过上述表达式就可逐次逼近正确解。
#Newton-Raphson for square root
#Find x such that x**2 - 24 is within epsilon of 0
epsilon = 0.01
k = 24.0
guess = k/2.0
while abs(guess*guess - k) >= epsilon:
guess = guess - (((guess**2) - k)/(2*guess))
print('Square root of', k, 'is about', guess)
Square root of 24.0 is about 4.8989887432139305
* 二分搜索也可以求平方根,但牛顿大大的方法更加高效 *
变量作用域
再次推荐上pythontutor.com体验一下。
程序使用符号表来记录各种变量的名称(symbol table)
每每调用一个函数时都会生成一个栈帧stack frame(本质上是一个新的符号表)来记录本函数的变量名,这个每个函数对应的符号表又被叫做命名空间或者作用域。
但调用一个stack frame中没有的变量时可以去完成寻找
a = 10
def f(x):
return x + a
a = 3
f(1)
4
上述代码中 函数f定义后解释器首先检查是否有语法错误,然后就存储起来待用
不管调用时a的值为多少,函数定义本身并没有被执行,而调用时a = 3。
x = 2
def g():
print(x)
x = 1
g()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-27-3ab2ca725f87> in <module>()
3 print(x)
4 x = 1
----> 5 g()
<ipython-input-27-3ab2ca725f87> in g()
1 x = 2
2 def g():
----> 3 print(x)
4 x = 1
5 g()
UnboundLocalError: local variable 'x' referenced before assignment
上述代码中:
函数g中本身有对x的赋值语句 x = 1,而有想用外面的x,这是不被允许的。
只要函数g中出现了对变量x的赋值语句,那么这个变量间被视为函数内部的x,就不再指向外面的x。
那么函数里面就不能修改外面变量的值了吗?
但是list可以!!!
百思不得其解
x = [1]
def g():
x[0] = x[0] + 2
g()
print(x)
[3]
x = 1
def g():
x = x + 2
g()
print(x)
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
<ipython-input-29-885a784499f9> in <module>()
3 x = x + 2
4
----> 5 g()
6 print(x)
<ipython-input-29-885a784499f9> in g()
1 x = 1
2 def g():
----> 3 x = x + 2
4
5 g()
UnboundLocalError: local variable 'x' referenced before assignment
文档 specification 函数的使用说明
- 本质上可以看作是调用函数的用户和编写函数的程序员之间的约定。
核心内容为“我给你什么样的输入,你给我什么样的输出”。
见如下例子。
def findRoot(x, power, epsilon):
"""Assumes x and epsilon int or float, power an int,
epsilon > 0 & power >= 1
Returns float y such that y**power is within epsilon of x.
If such a float does not exist, it returns None"""
pass
装逼利器 递归 Recursion
与数学归纳法及其类似,递归只需要记住两点:
- 基础情况 base case
- 递归情况(归纳情况) recursive case (inductive case)
def fact(n):
if n == 1: #base case
return 1
else:
return n * fact(n-1) #递归关系
fact(5)
120
* 递归的收敛性证明和数学归纳法完全一样: *
先证明特殊情况;假设 k情况处理,推到出k+1也成立的结论。
helper function 辅助函数
辅助函数是指在函数内定义的函数,这些函数可在本函数内使用,而不能再其他地方使用。
boolean 表达是在计算时也会和C语言一样存在短路的现象
import 导入模块
import math
print(math.pi)
3.141592653589793
from math import pi
print(pi)
3.141592653589793
from math import * #math里面的所有东西都
print(pi)
3.141592653589793
import math as m #math里面的所有东西都原封不动的复制到了这里
print(m.pi)
3.141592653589793
注意:一个源文件可以作为一个模块被导入。但如果你打开一个python命令行并以模块的形式导入了某个源文件,若此时修改源文件将不会影响已经导入的模块的行为,这时需要重新打开一个新的python命令行并在此导入该模块,才能使得修改生效。
多重赋值
a, b, c = 'xyz'
e,f = (1,2)
g,h = [3,4]
print(a,b,c,e,f,g,h)
x y z 1 2 3 4
def f():
return 1,2
print(type(f())) #1,2相当于(1,2)
<class 'tuple'>
Mutable 可变数据结构
list和dictionary不同于整数int、字符串str和tuple,是一种mutable对象。
int 和 str 本质上不可变,a = 3, b = 3, 变量a和b都指向整数3,但整数3本身永远也不会被修改为其他东西。
a = 3, a = a + 2,本来变量a指向整数3,但通过等号赋值后又指向了新的整数5,而整数3本身没有变化。
上述性质对list来说不成立,如下程序可知,L和Lr指向相同的list,但这个list本身是可变的(Mutable),改变这个list,也将同时改变这两个变量。
L = list(range(1,5))
Lr= L
print('L = ',L)
print('Lr = ',Lr)
L.reverse()
print('L = ',L)
print('Lr = ',Lr) #为什么Lr也被转过来呢
L = [1, 2, 3, 4]
Lr = [1, 2, 3, 4]
L = [4, 3, 2, 1]
Lr = [4, 3, 2, 1]
从C语言的角度来看,list和指针有些类似,当 Lr = L 时,Lr指向L所指的list,而不是新建一个新的list Lr。 此时只有一个list,且这个list有两个alias 别名(或叫引用名)L和Lr。
为了生成一个新的list,不能单纯使用赋值 = ,而需要使用list生成式。
可变对象存在同名混淆(alias)的问题,即一个可修改的对象有多个名字,通过这些名字都能修改相同的内容。
L = list(range(1,5))
Lr= [x for x in L] #用这种列表生成式子就能拷贝一份独立的L
#Lr = L[:] #切片的方式也可以返回一个拷贝
L.reverse()
print('L = ',L)
print('Lr = ',Lr)
L = [4, 3, 2, 1]
Lr = [1, 2, 3, 4]
# list 的复制方法
L1 = [1,2,3]
L1_alias = L1
L2 = L1[:]
L3 = L1 + []
L4 = [x for x in L1]
L5 = list(L1)
L6 = L1.copy()
#id()能反映python解释器中对象的唯一标示
print(id(L1),id(L1_alias),id(L2),id(L3),id(L4),id(L5),id(L6))
2558318106440 2558318106440 2558317942984 2558318530184 2558317942408 2558318049224 2558317942280
# list嵌套时,情况变得更加复杂
L1 = [1]
L2 = [2]
L3 = [L1, L2] #这里进行赋值时不是把L1的值付给L3的第一个元素,而是将L1这的list作为L3的第一个元素,而L1本身是可以被修改的
L4 = L3[:] #这里虽然通过切片进行了赋值导致L3和L4不再指向相同的list,但L4的第一个元素仍然是L1
import copy
L5 = copy.deepcopy(L3)
L4.append(L1)
L1.append(3)
print(L3)
print(L4)
print(L5)
[[1, 3], [2]]
[[1, 3], [2], [1, 3]]
[[1], [2]]
上述代码中L1的别名有:L3[0]、L4[0]和L4[2];
L2的别名有:L3[1]和L4[1];
面对这种情况时可选择copy.deepcopy()的方法来应对嵌套的list的拷贝。
list嵌套时,情况变得更加复杂
L1 = [1,2,3]
L2 = [4,5,6]
L1.append(L2) # L1 = [1,2,3,L2]
L2.append(L1) # L2 = [4,5,6,L1]
print(L1,L2) #通过上面不断迭代根本无穷无尽
print(L1[3][3][3][3])
[1, 2, 3, [4, 5, 6, [...]]] [4, 5, 6, [1, 2, 3, [...]]]
[1, 2, 3, [4, 5, 6, [...]]]
def removeDups(L1, L2):
"""Assumes that L1 and L2 are lists.
Removes any element from L1 that also occurs in L2"""
for e1 in L1: #这里的list被修改了
if e1 in L2:
L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,5,6]
removeDups(L1, L2)
print(L1)
[2, 3, 4]
上述代码中,第一次循环中e1 = 1,并执行了L1.remove(e1)语句,L1变为了[2,3,4]。第二次循环时e1将去取L1的第二个元素L1[1] = 3。
def removeDups(L1, L2):
"""Assumes that L1 and L2 are lists.
Removes any element from L1 that also occurs in L2"""
for e1 in L1[:]: #使用切片拷贝一份 L1
if e1 in L2:
L1.remove(e1)
L1 = [1,2,3,4]
L2 = [1,2,5,6]
removeDups(L1, L2)
print(L1)
[3, 4]
list.extend() 方法
L1 = [1,2,3]
L2 = [4,5,6]
L1.extend(L2) #与append方法不同,extend类似于 +
print(L1)
[1, 2, 3, 4, 5, 6]
list 生成式
mixed = [1, 2, 'a', 3, 4.0]
print([x**2 for x in mixed if type(x) == int])
[1, 4, 9]
mixed = [1, 2, 'a', 3, 4.0]
g = (x**2 for x in mixed if type(x) == int)
print(g)
print(next(g))
print(next(g))
print(next(g))
print(next(g))
<generator object <genexpr> at 0x00000253A7AD22B0>
1
4
9
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-47-b6453d51f256> in <module>()
5 print(next(g))
6 print(next(g))
----> 7 print(next(g))
StopIteration:
字符串的重要方法 join 和 split 方法
print('My favorite professor--John G.--rocks'.split(' '))
print('My favorite professor--John G.--rocks'.split('-'))
print('My favorite professor--John G.--rocks'.split('--'))
['My', 'favorite', 'professor--John', 'G.--rocks']
['My favorite professor', '', 'John G.', '', 'rocks']
['My favorite professor', 'John G.', 'rocks']
print(' '.join(['My', 'favorite', 'professor--John', 'G.--rocks']))
print('-'.join(['My favorite professor', '', 'John G.', '', 'rocks']))
print('--'.join(['My favorite professor', 'John G.', 'rocks']))
My favorite professor--John G.--rocks
My favorite professor--John G.--rocks
My favorite professor--John G.--rocks
Dictionary的Key只能是不可变(immutable)类型的量,list和dict都不能作为key
To quote Edsger Dijkstra, “Program testing can be used to show the presence of bugs, but never to show their absence!”
Edsger Dijkstr 曾说过 “软件测试只能被用于证明有bug,永远没法证没bug”。
程序测试过程之中出错证明有bug,但是通过测试的程序也不能保证没有bug。
程序测试的本质是使用有代表性的输入组成测试集(test suit)
程序在测试集上的运行结果若期望相一致,则通过测试。
但是如何选择测试集就是个玄学的问题。主要有两种思路:
黑盒子测试:在只知道程序的功能的情况下,去选择测试集。即通过浏览说明程序功能的描述文档,来寻找测试集,尽量包含各种边界和极端情况。白盒子测试不允许测试人员看程序代码本身,能避免被程序本身的思维漏洞影响,而只关注程序能否实现其功能。黑盒子测试通常由专门的程序测试人员来进行。
白盒子测试:在了解程序代码的情况下,选择测试集。例如程序通常用包含多个分支,白盒子测试就要保证每个分支都被测试集覆盖( path-complete )。具体而言,白盒子测试有以下讲究:
- if else elif 等存在分支语句的地方,测试集需要覆盖每个分支
- 有利用try except等错误处理机制来控制程序运行的地方,确保测试集能覆盖所有 raise except情况。
- 对于 for 循环,要测试不进入该循环的情况,循环仅进行1次的情况,循环执行多次的情况。有break时,确保测试集覆盖每个break。
- while 循环在上述for循环基础上,还需要额外测试while循环的不同退出条件。例如
while len(L) > 0 and not L[i] == e
需要确保每个退出的情况都被测试。 - 对递归函数来讲,需要测试递归函数仅被执行一次的情况,和递归多次的情况。
单元测试、整体测试、测试驱动程序(test driver)、测试桩(test stub)和 SQA
为了保证软件的质量,软件程序需要进行很多的测试,首先程序的开发团队会对每个某块进行测试,即进行单位测试(unit test)。
在每个某块都通过单位测试后,需要进行整体测试(integration testing),如果整体测试不通过,那么将定位bug位置在哪个某块,修改该某块并在此进行单位测试和整体测试。
通常有专业的质量保证团队来测试软件,即公司的QA部门。
软件测试有专门的测试工具,具有一定自动测试的能力,这些测试工具被称为测试驱动程序(test driver)。测试驱动程序主要测试程序被调用时表现出来的功能是否正常。测试驱动程序首先需要模拟程序被调用的环境,然后用自动生成或预先人为定义好的测试集作为输入进行大量测试。将这些测试结果和期望的结果之间进行比较,以此判断是否通过测试。
测试桩:
在进行单位测试时,即仅仅测试本模块的功能。然而一个模块常常需要使用到其他模块的功能,即和其他模块有交互,而往往这时,其他模块还没有实现,那就需要测试桩(test stub)来模拟这些模块的行为。测试桩模拟程序调用的其他模块的功能。
回归测试 regression testing:
一旦修改了代码需要再次仅软件测试。
Defensive Programming 防御式编程
若程序的执行过程中出现了错误的输入或者错误的中间变量,程序会直接报错或者主动去处理这些错误,而不是让程序错误地继续执行下去,最终得到错误的结果。
其主要目的是将潜在的隐形bug,变为显性bug,利用asset 和 raise 等手段。
Not a Number NaN
float("nan")
nan
异常的抛出与捕获 raise try except else finally assert
程序报错就是程序发生了各种异常,不处理这些异常时程序会中断并报错。
而异常捕获是程序自动应对这些异常,因此又被称为异常处理。
raise Exception('出现了一个异常')
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-51-0d3a99677ce8> in <module>()
----> 1 raise Exception('出现了一个异常')
Exception: 出现了一个异常
try:
raise Exception('出现了一个异常')
except:
print('捕获到一个异常') #注意到这里处理了异常,并没有直接报错。
捕获到一个异常
try:
raise Exception('出现了一个异常')
except Exception as ex: #报错时会抛出一段说明字符串
print(ex) #这里将这个字符串赋值给了ex
出现了一个异常
异常(Exception)的种类有很多,包括 TypeError,IndexError, NameError 和 ValueError等
这些异常本质都是类,有一定的继承关系,都属于Exception
while True:
val = input('Enter an integer: ')
try:
val = int(val)
print('The square of the number you entered is', val**2)
break #to exit the while loop
except ValueError:
print(val, 'is not an integer')
Enter an integer: 1
The square of the number you entered is 1
try:
raise NameError('A Name Error!')
except ValueError:
print('A Value Error!')
except:
print('不是ValueError的其他异常')
不是ValueError的其他异常
def fancy_divide(numbers,index):
try:
denom = numbers[index]
for i in range(len(numbers)):
numbers[i] /= denom
except IndexError,KeyError: #有try程序体有IndexError和KeyError时执行 except从句
print("-1")
else: #没有IndexError和KeyError时且没有其他错误时执行else从句
print("1")
finally: #不管有没有IndexError和KeyError都执行finally
print("0")
File "<ipython-input-56-b08ab0f94fcb>", line 6
except IndexError,KeyError: #有try程序体有IndexError和KeyError时执行 except从句
^
SyntaxError: invalid syntax
fancy_divide([0, 2, 4], 1)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-57-27b8c89ffc53> in <module>()
----> 1 fancy_divide([0, 2, 4], 1)
NameError: name 'fancy_divide' is not defined
fancy_divide([0, 2, 4], 4)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-58-523866195c76> in <module>()
----> 1 fancy_divide([0, 2, 4], 4)
NameError: name 'fancy_divide' is not defined
fancy_divide([0, 2, 4], 0) #有了其他异常
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-59-28a5f7d4513f> in <module>()
----> 1 fancy_divide([0, 2, 4], 0) #有了其他异常
NameError: name 'fancy_divide' is not defined
注意上面的else语句:只有没有IndexError和KeyError时且没有其他错误时执行else从句
try:
try:
raise Exception()
except:
print('1')
except:
print(2)
1
上述代码中,存在嵌套的try except,其中由于异常已经在内层被处理,没有向外层抛出错误。
#内层处理不了的异常将向外抛出
try:
try:
raise Exception()
except ValueError:
print('1')
except:
print(2)
2
dot . 表达式
类中的方法需要使用 ‘.’表达式调用。
L1 = [1, 2, 3]
L1.reverse()
print(L1)
list.reverse(L1) #L1.reverse()本质上就是list.reverse(L1)
print(L1)
[3, 2, 1]
[1, 2, 3]
int.__add__(1,2) #本质是1+2
3
继承 多重继承
左边优先
class C():
def f(self):
print('1')
class D():
def f(self):
print('2')
class A(C,D):
pass
a = A()
a.f()
1
归并排序 O(nlogn)
def merge(left, right, compare):
"""Assumes left and right are sorted lists and
compare defines an ordering on the elements.
Returns a new sorted (by compare) list containing the
same elements as (left + right) would contain."""
result = []
i,j = 0, 0
while i < len(left) and j < len(right):
if compare(left[i], right[j]):
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
while (i < len(left)):
result.append(left[i])
i += 1
while (j < len(right)):
result.append(right[j])
j += 1
return result
def mergeSort(L, compare = lambda x, y: x < y):
"""Assumes L is a list, compare defines an ordering
on elements of L
Returns a new sorted list with the same elements as L"""
if len(L) < 2:
return L[:]
else:
middle = len(L)//2
left = mergeSort(L[:middle], compare)
right = mergeSort(L[middle:], compare)
return merge(left, right, compare)
print(mergeSort([5,7,3,1,8,9,1]))
[1, 1, 3, 5, 7, 8, 9]
类属性和实例属性
下面例子中,类属性A.a相当于每个A的实例所公用的属性,只要A的实例中没有单独定义属性a,那么这个实例将继承类中的属性。
class A():
a = 1
def __init__(self,b):
self.b = b
def f(self):
A.a += 1
x = A('X')
y = A('Y')
x.f()
print(y.a)
y.a = 8
print(x.a,y.a,A.a)
2
2 8 2