文章目录
目录请见详细目录
2.1.报错、错误与试错
2.1.1.错误和常见的错误类型
在Python领域写程序,无论作为新手上路,还是高手一枚,总是不可避免的遇到这样那样的各种错误,我们熟知的错误就包括:
错误名称 | 导致原因 |
---|---|
SyntaxError | 条件或循环等末尾没有冒号 |
---------------- | 条件判断中的==写成了= |
---------------- | 将关键字作为变量进行了声明 |
DivisionbyzeroError | 除法中的除数为0 |
IndentationError | 缺失或多余的缩进块 |
ValueError | 类型正确但值不符合要求 |
TypeError | 对str和int类型进行拼接 |
----------- | 将bool类型加减乘除 |
----------- | 修改str或tup的部分内容 |
----------- | 调用方法时传入的参数不符合传入要求(或多/少传入了参数) |
----------- | str没加引号 |
NameError | 使用了未定义的变量(对象)直接计算或在声明global变量之前无定义 |
IndexError | 索引超过了list/str/tuple的最大索引 |
KeyError | 调用了dict的未知名称 |
AttributeError | 调用了对象的未知属性或方法 |
2.1.2.try和except嵌套
在程序运行过程中,如果遇到了错误,程序会自动结束并报出错误.我们也可以使用raise来手动抛出错误,例如:
raise ValueError("一个ValueError")
可以自定义错误内容,但类型还是属于ValueError
.如何自定义错误?请看教程1.2.4..那么如何让程序在出现错误后仍然运行呢?此时我们就要用到try...except
语句了.在try
下缩进的代码运行时,如果出现了错误,则会停止该缩进块而去运行except下内容,语法格式如下:
try:
print("1")
raise IndexError
print("2")
except IndexError:
print("3")
except Exception as e:
print(e)
在打印出1以后遇到了IndexError
,因此跳出try
缩进块.先检索第一个except
,发现就是IndexError
,于是打印3,离开try...except
块.此过程中不会报错,只会依次打印1和3.
如果我们将raise
出的错误更改一下,像这样:
try:
print("1")
raise ValueError
print("2")
except IndexError:
print("3")
except Exception as e:
print(e)
就会发现检索到的并不是IndexError
,因此进入第二个except
检索.这里有一个公共错误类Exception
,任何错误都继承于该类,因此检索成功.此时将错误信息保存在对象e
中,打印出错误.**注意!**在打印该错误时,虽然内容就是报错内容,程序仍未停止,可以继续运行.其实还可以这样:
try:
print("1")
raise IndexError
print("2")
except IndexError:
print("3")
except NameError:
print("4")
else:
print(5)
此时,前两个except
都没有检索到错误,因此进入else
块.else
块当且仅当try
下发生错误且所有except
语句均没有检索到错误时才会运行.
2.2.惰性计算和立即计算
此章较为重要 务必认真学习 在日后的人工智能等方面中,惰性计算发挥了巨大的作用!
2.2.1.迭代器与可迭代对象
2.2.1.1.什么是迭代器和可迭代对象
在学循环的时候,我们就知道如果要对1-30的正整数遍历,可以这样写:
for i in range(1,31):
pass
所以这个range
到底是个啥?[由于教程基于Python 3.7+,这里不再提及Python 2的一些细节和特性]如果我们直接使用:print(range(1,31))
,会发现打印结果就是range(1,31)
,类型为<class "range">
.而所谓range,其实就是我们的迭代器.对于循环语句,我们知道还可以这样写:
for i in [1,2,3]:
pass
这意味着让i
分别带入1,2,3进行计算.这里的列表[1,2,3]
就是可迭代对象.
2.2.1.2.常见的迭代器和可迭代对象
在Python中,迭代器能且只能为range
函数返回的结果,或者是一个类型为<class "range">
的对象.而可迭代对象则可以是任何group型的数据,或者一个字符串.例如,list
tuple
array
str
都可以作为可迭代对象使用.
2.2.1.3.迭代器和可迭代对象的转换
先来看看如何将range
转换为可迭代对象.很简单,直接将其存为list(或tup)即可:
a=range(1,31)
a=list(a)
#a=[1,2,3,...30]
b=[range(1,31)]
#b=[range(1,31)]
c=[for i in range(1,31)]
#c=[1,2,3,...30]
看出区别了吧?如果使用list类强制转型range类,就会直接释放其中的所有数字;而用列表储存不同,其仍然以range形式存在;而最后一种则是第一种的另一种等价写法,只不过其可以和下一节的lambda
函数一起使用来减少代码量.
那么如何将可迭代对象转换为迭代器呢?其实只需要将首尾和公差计算后即可转换!
2.2.2.从内存角度看惰性计算
什么是惰性计算?在上一节中,我们提到了这样一种列表创建方法:
mylist=[for i in range(1,31)]
如果我希望实现对于每个1-30的数都平方,然后减一呢?
原来的写法应当是这样的:
result=[]
for i in range(1,31):
j=i
j=j**2-1
result.append(j)
print(result)
如果从迭代器的角度看,可以这样写:
先定义方法:
def caculate(m):
return m**2-1
然后进行惰性计算:
result=[caculate(i) for i in range(1,31)]
和上面的代码结果是一样的.还可以进行多次操作,如:
n=range(1,31)
result1=[i**2 for i in n]
result2=[result1[i]-1 for i in n]
惰性计算,我们可以这样认为:如果直接创建列表[1,2,3,…30],那么要占用31个内存单位元,分别储存1-30的数字和list这个类型.而range则不同,range(1,31)只需要3个内存单位元,分别储存起始值1,末位置31和range这个类型.这样不仅大大节省了内存空间,而且减少了代码量,岂不妙哉?
最后,我们来官方地讲一下什么是惰性计算.所谓惰性计算就是不在迭代器给出第一时间分析出其中所有元素,而是以少量内存形式存在,一直保存迭代器形式直到所在代码段(如方法/循环)结束才把迭代器中的每一个元素拆开.这种方式虽说没有省下什么代码运行时间,但是大大缩小了(特别是数据量大的时候)内存消耗和代码量.事实上,定义的类下的除初始化外的所有方法,都是惰性计算.它们仅在被调用时被编译.而外部定义的方法则都是立即计算.
2.2.3.迭代器的惰性计算
迭代器作为python对象,同样也可以进行惰性计算.先看一段代码:
n=range(1,31)
a=[range(1,i+1) for i in n]
b=[list(a[i]) for i in n]
先看第一句.变量n
是一个迭代器.
第二句: 对于1-30,分别创建一个迭代器range(1,i).这意味着a这个列表中包含着30个惰性计算了的迭代器.
第三句:对于每一个迭代器都转换为列表,因此b
应该为:
b=[[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5],...,[1,2,3,...30]]
2.2.4.惰性计算的应用
目前而言,还没什么真正的应用,但当机器学习开始的时候,就会有大规模应用.这里仅展示一段代码(我的mml-qae库的源代码的一部分),有兴趣的同学可以尝试理解:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split as tts
from sklearn.neighbors import KNeighborsClassifier as knc
from sklearn.metrics import accuracy_score as acc
import mml_qae.MoreErrors as me
def max_of_list(l:list):
"""给定列表返回其中最大值"""
maxium=l[0]
for i in l:
try:
i=float(i)
except:
raise me.DataError2
for j in range(0,len(l)-1):
a=l[j];b=l[j+1]
if b>a:
maxium=b
return int(maxium)
def predict(path:str,to_name:str,need_predict:pd.DataFrame):
try:data=pd.read_csv(path,header=0)#读入csv数据
except:raise me.CSVError
try:
x_data=data.drop([to_name],axis=1)#从data中摘取特征组
y_data=np.ravel(data[[to_name]])#从data中摘取目标组
except:raise me.DataError3
x_trainset , x_testset , y_trainset , y_testset = tts(x_data,y_data,random_state=1)
#建立训练集(训练特征组 和 训练目标组)和测试集(测试特征组 和 训练目标组)
n=range(1,23)
KNNs=[knc(n_neighbors=i) for i in n]
scores=[KNNs[i].fit(x_trainset,y_trainset).score(x_testset,y_testset) for i in range(len(KNNs))]
best=max_of_list(scores)
bs=scores.index(best)+1
new=knc(algorithm="kd_tree",n_neighbors=bs)
new.fit(x_trainset,y_trainset)
ny=new.predict(need_predict)
return ny
def get_score(path:str,to_name:str):
data1=pd.read_csv(path,header=0)
x_data=data1.drop([to_name],axis=1)#从data中摘取特征组
y_data=np.ravel(data1[[to_name]])#从data中摘取目标组
x_trainset , x_testset , y_trainset , y_testset = tts(x_data,y_data,random_state=1)
n=range(1,23)
KNNs=[knc(n_neighbors=i) for i in n]
scores=[KNNs[i].fit(x_trainset,y_trainset).score(x_testset,y_testset) for i in range(len(KNNs))]
best=max_of_list(scores)
return best
2.3.lambda函数详解
2.3.1.什么是lambda
lambda
,又称匿名函数.在Python中,如果定义了一个方法m
,那么type(m)
就会是<class "function">
,而print(m)
会得到<function m at 0x00000228E0DAC268>
.这说明Python已经为你申请了变量m
的内存地址0x00000228E0DAC268.而lambda
只声明函数内容,不声明名称,如:
lambda x:x+1
此时,我们没法多次调用,而只能在定义后立即使用它来获取返回结果:
print((lambda x:x+1)(1))
#->2
那么想多次调用呢?直接给一个内存地址就好了呗:
func=lambda x:x+1
print(func(1))
这样语法就和普通的def没什么区别的.其中的x为传入实参,x+1为传出实参.lambda语句不需要return,因为它只能有一句代码,而这句代码就是返回值.如果不将其赋值给某一个变量,那么调用就要用tuple包括住所需要传入的实参.也可以传入多个参数,如:
(lambda x,y:abs(x-y))(3,2)
#->1
注意传入参数部分没有括号.整个lambda(包括返回值)都需要用小括号括住.
2.3.2.用lambda简化你的程序
先来看一段乱七八糟的代码:
def a(x,y,z):
m=x
if m<y:m=y
if m<z:m=z
return m
r=[]
for i1 in range(1,31):
for i2 in range(11,41):
for i3 in range(21,51):
r.append(a(i1,i2,i3))
print(r)
尝试简化[这里不给思路 看看能否看懂?看不懂可留言]
r=(lambda i1,i2,i3:i2max(max(i1,i2),i3))\
((range(10*j+1,10*j+31),\
range(10*j+1,10*j+31),\
range(10*j+1,10*j+31) for j in range(1,4)))
print(r)
虽说看起来复杂了点,思路也烦了些,但总之是省代码了.