python基础补充(二)


上节的内容,我们对数据的类型以及简单的运算过程进行了深入的讨论,这一节我们来讨论控制流程、函数、以及异常处理。接下来开启今天的旅程:

控制流程

在Python中,冒号用于标识一段代码块的开始,代码块作为控制结构的结构体。如果这个结构体可以被表述为一个可执行语句,那么这句话可以直接写在冒号后面:

for i in range(10):print(i,end=' ')
# 输出为:0 1 2 3 4 5 6 7 8 9 

结构体通常从冒号的下一行开始进行整齐的缩进,Python中缩进有着实际的意义,一段整齐的缩进类似于c语言中的大括号。

循环语句

python为我们提供了两种循环语句,分别是while和for。while允许以布尔条件的重复测试为基础,进行一般的重复。for则是定义序列的值,提供适当的迭代。下面我们看看两者具体的不同:

while循环

while循环的语法是这样的:

while condition:
	body

while循环最大的特点就是它的判断条件,condition可以是任的布尔结果表达式。这样的循环方式为否出是循环。
下面我们用一个倒叙输出列表内容的代码体会一下while的实际应用:

i=-1
a=list('accoding')
# 倒叙输出,遇到第一个c停止
while i>=-len(a) and a[i]!='c':
    print(a[i],end='')
    i-=1
# 输出右往左第一个c的位置
print('\n',i+len(a),sep='')
# 输出为:gnido
#        2

for循环

for循环是比while循环更为便利的选择。for的循环语法可以使用在任何的可迭代结构中,如列表,元组,字符串,集合,字典或文件。后面还会具体提到迭代类型。其一般于法如下:

for val in iterable:
	body

for在遍历可迭代对象时有着独特的优越性。需要注意的是,val被视为一个标准的标识符。如果原始可迭代对象中元素是可变的,我们就可以调用它的方法:

a=[[1,2,2,3,5],[2,5,6,6,8],[6,6,4,1,1],[2,2,3,5,6]]
for i in a:
    i.append('x')
    print(i)
# 输出为:[1, 2, 2, 3, 5, 'x']
#        [2, 5, 6, 6, 8, 'x']
#        [6, 6, 4, 1, 1, 'x']
#        [2, 2, 3, 5, 6, 'x']

for循环的简洁性体现在他不需要构建布尔循环条件,也不需要管理明确索引。但这也注定了它不能很好地构建某些特殊的循环条件。同时,因为遍历可迭代对象不需要依据下标,所以如果我们想要知道某个元素对应的索引,就需要自己创建一个累加器或者使用索引值进行遍历。
为此,python提供了一个range的内置类,它本身是一个生成器(后面会具体讨论),可以按照我们的需求生成一个整数序列,方便我们按照索引遍历可迭代对象:

a=[5,2,6,4,9,3,1,7,10,8]
for i in range(10):
    print(str(i)+':'+str(a[i]),end='  ')
# 输出为:0:5  1:2  2:6  3:4  4:9  5:3  6:1  7:7  8:10  9:8 

break和continue语句

python中同样支持这两种停止语句,其意义也和c语言中一样,break会直接结束循环,而continue会跳出本次循环进行下一次。我们利用这两个语句找到十以内奇数:

i=0
while True:
    i += 1
    if i%2==0:
        continue
    elif i==10:
        break
    else:print(i)

这样就能够输出13579了。

函数

对于函数的详细讲解之前已经详细介绍过,这里只会稍作补充。Python由于是一种动态类型语言,因此无需指定参数类型以及返回值类型,但是对于函数的错误使用只有在运行时才能被监测到。每次调用函数时,python会创建一个专用的活动记录用来存储与当前调用相关的信息。

return语句

如上面介绍的,python的返回值和参数的类型因为没有明确规定,所以自然可以是不同的类型。这就代表我们传参时并非只能传递同一类型的参数,返回值也同样如此。举个例子:

def sum_(a):
    sum=0
    try:
        for i in a:
            sum+=i
        return sum
    except:
        return "false"
print(sum_([1,2,3,4,5]))
print(sum_(['a','b']))
# 输出为:15
#        false

由以上内容可以看出,我们的返回值可以是整数类型,也可以是字符串类型,当然接收参数的类型也是同理。这也就解释了为什么有些类型错误导致的问题只有在函数运行的过程中才能被检测到。当然,和C语言中一样的是,我们的函数体一定要放在调用之前。

同步修改实参

接下来我们说点不一样的。如果形式参数接收到了一个实例,而后面我们继续调用专属方法对该实例进行修改的话,不需要通过返回操作也可以将该形参的结果传递下来。看一个例子:

def fun(n,a):
    for i in range(n):
        a.append(i)
b=[]
fun(10,b)
print(b)
# 输出为:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这就是因为a和b都只是一个标识符,虽然作用空间不一样,但是其代表的实际地址是相同的,因此通过该类的方法可以直接修改地址中值。
但是需要注意的是,这种同步的修改只局限于使用参数的类的内置方法对参数实例的修改。换句话说,如果我们使用如下方法试图修改:

def fun(n,a):
    a=a*n
b=[1,2,3] # 这里的b与函数中的a代表的内容地址都相同,
# 标识符不同并不影响对其所代表的内容的修改
fun(10,b)
print(b)
# 输出为:[1, 2, 3]

可以看到,这就无法改动了。
此外,我们不可以在函数中改变传递参数的类型:

def fun(n,a):
    for i in range(n):
        a.append(i)
    a=tuple(a) # 此处a被改变成了元组类型,因此从这句话之后,函数内a的变化
    # 将无法影响函数外的a了。即使后面重新转换为列表也是不行的。
    a+=a
    print(a) # 函数中的a已经是元组类型了
a=[]
fun(10,a)
print(a) # a确实被正确添加了内容,但是不接受改变类型以及之后的操作
# 输出结果为:
# (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这其中原因,和上一节中的相等运算符里介绍的一样。我们改变了a的类型,a的实际地址就变化了,而实际地址一旦变化,就不能产生形参实参同步变化的效果了。
这些内容只做拓展,大家尽量不要在实际编程之中使用。

关键字参数

在之前的讲述中,我们详细介绍了位置参数,默认参数,可变参数和字典传参,还介绍了传参的顺序。但是如果我们把默认参数放到可变参数之后,是不是真的无法修改了呢?实际上并不是:

def print(self, *args, sep=' ', end='\n', file=None):

这一段是print的参数表。可以看到,我们待打印的内容将会全部被args这个可变参数接收。但是,大家还记得如何修改sep和end的值吗?
重置sep和end本身就是一种关键字传参,我们可以用这种方式来改变指定位置的参数值。比如:

def add_(a,b=15,c=0):
    return a+b+c
print(add_(10,c=5))
# 输出为:30

这样,就可以跨过默认参数b直接给c赋值。
同时注意一下,默认参数除了可以是整形,字符型,也可以是字典,列表型等。

异常处理

异常是程序执行期间发生的突发性事件,在执行代码时遇到突发状况也会引发异常。如果上下文有处理异常的代码,异常可能会被捕获。如果没有被捕获,异常可能会导致解释器停止运行程序,并向控制台发送合适的信息。这一节会讨论常见的错误类型、捕获异常以及异常处理的相关内容。

常见的错误类型

python有大量的异常类,它们定义了各种不同的类型异常。下面我们要给出一些常见的异常:

异常类名 描述
Exception 所有异常的基类
AttributeError 如果对象obj没有foo成员,会由语法obj.foo引发
EOFError 一个"end of file"到达控制台或者文件输入引发错误
IndexError 索引超出序列范围引发错误
KeyError 请求一个不存在的集合或字典关键字引发的错误
KeyboardInterrupt 用户使用ctrl+c中断程序引发错误
NameError 是用不存在的标识符引发错误
StopIteration 下一次遍历的元素不存在时引发的错误
TypeError 发送给函数的参数类型不正确引发的错误
ValueError 函数参数支非法引发的错误
ZeroDivisionError 除数为0引发错误

抛出异常

当程序执行到带有异常的实例raise语句时,会抛出一个异常,而异常没有得到处理的时候,程序就会停止,并有可能将异常传播到调用的上下文。比如:

def checkout ():
    for i in range(1,5):
        if i%4==0:
            raise ValueError('不接受4的倍数')
        print(i)
# 调用函数进行测试
checkout()

输出结果为:
在这里插入图片描述
从这个错误提示中,我们可以找到抛出错误的位置以及调用的上文。

捕捉异常

出现错误并不可怕,而解决错误的方法通常有两种,第一种是三思而后行,即在运行程序之前,排查掉可能引发错误的问题。比如:

...
print(x/y)

为了避免出现“ZeroDivisionError”错误,就需要检查运行过程中y是否会出现为0的状况。
另一种被程序员接受的理念是请求源两笔得到许可更容易。该观点认为我们不需要花费额外的时间维护每一个可能发生的异常,而只需要在异常发生时,有一个解决机制就可以了。这一理念时使用try-expect控制结构来实现的。因此,在我们无法完全排除异常的可能性或者主动评估条件来避免异常代价更高时,最好使用这一结构进行异常处理,下面我们来看看常见的用法:

try-except用法之1

def checkout (n):
    for i in range(n):
        if i>=4:
            raise ValueError('不要传入比4大的数')
        print(i)
# 调用函数进行测试
try:
    checkout(5)
except ValueError as e:
    print('数值大了:',e)

看到输出结果:
在这里插入图片描述
这样就没有报错了,程序也可以继续运行,并且我们收到了一个提示,因为我们调用checkout函数时由于参数过大引发了错误,应该选择更小的参数进行传递。我们要记住这种用法。

try-except用法之2

except还可以做以下变形:

def checkout (n):
    if n<=1:
        raise StopIteration('不接受小于1的参数')
    for i in range(n):
        if i>=4:
            raise ValueError('不要传入比4大的数')
        print(i)

# 调用函数进行测试
try:
    checkout(-1)
except (ValueError,StopIteration):
    print('参数值不对!')
# 运行结果为:参数值不对!

这样将可能出现的错误使用元组打包的方式,同样被允许,并且可以一次性对多种类型的错误进行处理,不过如果我们想要在解决问题的同时知道具体是哪种错误引起的问题,这样的方式就显得有些力不从心了。

try-except用法之3

为了解决上述的困难,我们还可以用以下的方法处理:

def checkout (n):
    if n<=1:
        raise StopIteration('不接受小于1的参数')
    for i in range(n):
        if i>=4:
            raise ValueError('不要传入比4大的数')
        print(i)

# 调用函数进行测试
try:
    checkout(-1)
except ValueError as e1:
    print('参数值不对:',e1)
except StopIteration as e2:
    print('参数值不对:',e2)

这样我们就可以找到具体问题,有针对性的进行纠正了。

try-except用法之4

当然,如果我们希望直接无视掉出现的问题,而选择直接执行下一步的操作,还可以这样做:

def checkout (n):
    if n<=1:
        raise StopIteration('不接受小于1的参数')
    for i in range(n):
        if i>=4:
            raise ValueError('不要传入比4大的数')
        print(i,end=' ')

# 调用函数进行测试
for n in range(5):
    try:
        checkout(n)
        print()
    except :
        pass
# 输出结果为:0 1 
#           0 1 2 
#           0 1 2 3 

从这个结果中不难看出,我们自动无视掉了n=0和n=5时会引发的错误。当我们不需要对错误进行处理也不需要知道错误的发生时,就可以使用这样的语法。需要注意的是,由于我们处理一个未知类型的异常会比较困难,所以在实战中这种用法并不多见。

try-except用法之5

除此之外,我们还可以使用捕获语句之后重新抛出异常:

def checkout (n):
    if n<=1:
        raise StopIteration('不接受小于1的参数')
    for i in range(n):
        if i>=4:
            raise ValueError('不要传入比4大的数')
        print(i)

# 调用函数进行测试
try:
    checkout(5)
except ValueError as e1:
    print('参数值不对:',e1)
    raise
except StopIteration as e2:
    print('参数值不对:',e2)

输出会有这样的变化:
在这里插入图片描述
这种方法通常可以在补充更准确的异常信息,或希望异常能够中断程序并指引我们找到错误位置时使用。
今天分享的内容就这么多啦,我们主要是对控制流程,函数和异常处理的内容进行了一个详细的补充,细小的知识点很多,但是并不难,希望大家能够坚持看完,并对之前学过的知识进行一个系统性的复习~

猜你喜欢

转载自blog.csdn.net/weixin_54929649/article/details/124259489