错误和异常
错误
- 语法错误(syntax error)
- 逻辑错误( logic error)
- 执行期间错误(runtime error)
demo:
for i in range(10)
print(i)
out:
for i in range(10)
^
SyntaxError: invalid syntax
此过程由Python的语法分析器完成,检测到错误所在文件和行号。以向上箭头标记错误位置,最后显示错误类型。
当程序检测到一个错误,解释器就无法继续执行下去,抛出异常,终止程序。
异常
系统根据不同的错误抛出不同的异常。
常见异常:
异常 | 描述 |
---|---|
NameError | 尝试访问一个没有申明的变量 |
ZeroDivisionError | 除数为 0 |
SyntaxError | 语法错误 |
IndexError | 索引超出序列范围 |
KeyError | 请求一个不存在的字典关键字 |
FileNotFoundError | 文件未发现错误(比如你要读的文件不存在) |
AttributeError | 尝试访问未知的对象属性 |
ModuleNotFoundError | 模块未发现 |
IndentationError | 缩进 |
异常处理:
程序一旦发生错误,程序就无法继续运行。
为了使程序健壮,可做相关异常处理。
我们使用try…except…
语法:
try :
<语句>
except [<异常的名称>,[,<异常类的实例变量名称>]]:
<异常的处理语句>
[else:
<没有异常产生时的处理语句>]
[finally:
<最后都会执行的语句>]
- try子句
- try … except 必须放在可能发生异常的程序段周围,而try_statements则是可能发生异常的程序段。
- except子句
- 用来捕捉指定的异常,一旦捕捉到,就执行与之对应的except_statements,即用来处理异常的程序语句。
- 如果要针对不同的异常做不同的处理,可以使用多个except子句,其中,exceptionType是欲捕捉的异常类型,省略不写,表示为预设类型:BaseException,所有异常都继承自该类别。
- [as identifier]可以将捕捉到的异常指向一个变量,然后,通过该变量获得异常相关的信息。
- 不带任何异常类型使用except,将捕获所有发生的异常。不推荐这么使用,因为我们不能通过该程序识别出具体的异常信息。
- else子句
- 当try_statements没有异常发生时,会跳过except子句,执行else_statements。
- 该子句为可选语句,可以指定或者省略。
- finally子句
- 当要离开try … except 时(无论异常是否发生),就会执行finally_statements,可以使清除错误或者收尾的语句。可给可忽略。
demo:
try :
x = int(input('请输入被除数x:\t'))
y = int(input('请输入除数:\t'))
z = x/y
except Exception as e:
print(e.args)
else:
print('未捕捉到异常,X/Y = ',z)
finally:
print("leave this module")
out:
请输入被除数x: 24
请输入除数: 0
('division by zero',)
leave this module
raise(触发异常)
除了系统抛出的异常,我们可以用raise语句自己触发异常。
格式:
raise [Exception,[,args,[,traceback]]]
- Exception:异常类型
- args:我们自己提供的异常参数
- traceback:可选,如果存在,
try :
raise NameError('Sorry,Error occurs')
except Exception as e:
print(e.args)
else:
print('未捕捉到异常,X/Y = ',z)
finally:
print("leave this module")
out:
('Sorry,Error occurs',)
leave this module
assert(断言)
assert condition
逻辑上相当于:
if not condition:
raise AssertionError()
为断言添加一个异常参数
assert experssion [,args]
arguments
userid = 'lzh'
assert userid == '111','用户名错误'
out:
assert userid == '111','用户名错误'
AssertionError: 用户名错误
函数
函数定义
在Python中,函数有五大要点,分别是def、函数名、函数体、参数、返回值,以及两个英文版符号,分别是括号(括号内为参数)和冒号(:)。
def:函数的关键字,没它可不行。
函数名:函数的名称,根据函数名调用函数。
函数体:函数中进行一系列的具体操作。
参数:为函数体提供数据。
返回值:当函数执行完毕后,可以给调用者返回数据。
上述函数的要点中,最重要的是参数和返回值。
1.返回值
函数是一个功能块,该功能到底执行成功与否,需要通过返回值来告知调用者。
2.参数
定义函数时,参数是一定需要考虑的。Python的函数定义非常简单,但灵活度却非常大。
对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。
Python中,参数类型有:必选参数、默认参数、可变参数、关键字参数和命名关键字参数。函数中,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
还有的是实参和形参的区别:
- 实参:实参是一个确定的值,能够传递给形参,可作为位置参数或者关键字参数传递
- 形参:只有在调用时才分配内存单元。调用结束后,即释放所分配的内存单元。因此,形参只在内部有效,函数调用结束后返回主调用函数后
3.空函数
空函数:什么事也不做,可以用pass语句。既然“一事不做”,那空函数还有什么用处?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。如此,运行代码程序就不会出现错误了。
1 #空函数
2 def nop():
3 pass
函数参数
Python中,参数是非常灵活的。掌握参数就能领悟函数的真谛了。这是真的。参数是比较难理解的,特别是参数组合。
1.位置参数
既然说函数,就需要展示函数:
1 #位置参数(必选参数)
2 def involution(x):
3 return x * x
4 >>>involution(3)
5 9
6 >>>involution(5)
7 25
如代码所示,参数x就是一个位置参数。
2.默认参数
Python函数支持默认参数,即可以给函数的参数指定默认值。当该参数没有传入相应的值时,该参数就使用默认值。
1 #默认参数
2 def involution(x,n = 2):
3 s = 1
4 while n > 0:
5 n = n - 1
6 s = s * x
7 return s
8 >>>involution(6)
9 36
10 >>>involution(5,3)
11 125
注:1.设置默认参数时,必选参数(位置参数)在前,默认参数在后,否则Python的解释器会报错;
1 >>>def add_end(L=[]):
2 ... L.append('END')
3 ... return L
4 ...
5 >>>add_end()
6 ['END']
7 >>>add_end()
8 ['END','END']
9 >>>add_end()
10 ['END','END','END']
上述代码展示的是默认参数不为不可变对象的现象。因此,默认参数必须指向不可变对象【字符串、None……】。
3.可变参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:
1 #一般性函数
2 def calc(numbers):
3 sum = 0
4 for n in numbers:
5 sum = sum + n * n
6 return sum
如何调用calc()函数呢?需要调用时,需要为参数引入list或者tuple。
1 #函数调用
2 >>> calc([1, 2, 3])
3 14
4 >>> calc((1, 3, 5, 7))
5 84
然而,如果我们使用可变参数,我们可以进行简化,方法如下:
1 #可变参数
2 def calc(*numbers):
3 sum = 0
4 for n in numbers:
5 sum = sum + n * n
6 return sum
1 #可变参数的魅力
2 >>> calc(1, 2, 3)
3 14
4 >>> calc(1, 3, 5, 7)
5 84
6
7 #参数调用不用calc([1,2,3]),括号内还用写中括号,好麻烦~~~
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数*:
1 >>> calc(1, 2)
2 5
3 >>> calc()
4 0
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
1 >>> nums = [1, 2, 3]
2 >>> calc(nums[0], nums[1], nums[2])
3 14
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
1 >>> nums = [1, 2, 3]
2 >>> calc(*nums)
3 14
4.关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。dict就是字典,它是键值对组合,益处多多~~~
1 #引入关键字参数,默认为**kw
2 def person(name, age, **kw):
3 print('name:', name, 'age:', age, 'other:', kw)
函数person除了必选参数name和age外,还接受关键字参数kw。在调用该函数时,可以只传入必选参数(必选参数必须全部传入,否则会出错),也可以传入关键字参数。注:关键字参数可是任意个的。
1 #调用关键字参数
2 >>>def person(name,age,**kw):
3 ... print('name:',name,'age:',age,'other:',kw)
4 ...
5 >>>person('Jack')
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 TypeError: person() missing 1 required positional argument: 'age'
9 >>>person('Jack',36)
10 name:Jack age:36 other:{}
11 >>>person('Jack',36,city='Hangzhou')
12 name:Jack age:36 other:{'city':'Hangzhou'}
13 >>>person('Jack',36,city='Hangzhou',job='Engineer')
14 name:Jack age:36 other:{'city':'Hangzhou','job':'Engineer'}
关键字参数有什么用呢?其实,既然存在就有它的强大之处。
它可以扩展函数的功能。比如,在person函数里,我们保证能接收到name和age这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
如何操作呢?我们可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'}
2 >>> person('Jack', 36, city=extra['city'], job=extra['job'])
3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
当然了,上面代码调用方式有点烦,通过dict键来查找值。我们可以通过关键字简化一下:
1 >>> extra = {'city': 'Hangzhou', 'job': 'Engineer'}
2 >>> person('Jack', 36, **extra)
3 name: Jack age: 36 other: {'city': 'Hangzhou', 'job': 'Engineer'}
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数,kw将获得一个dict。
5.命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw
检查。
1 def person(name, age, **kw):
2 if 'city' in kw:
3 # 有city参数
4 pass
5 if 'job' in kw:
6 # 有job参数
7 pass
8 print('name:', name, 'age:', age, 'other:', kw)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
1 def person(name, age, *, city, job):
2 print(name, age, city, job)
和关键字参数*kw不同,命名关键字参数需要一个特殊分隔符,*后面的参数被视为命名关键字参数。
调用命名关键字参数方式如下:
1 #调用命名关键字参数
2 >>> person('Jack', 36, city='Hangzhou', job='Engineer')
3 Jack 36 Hangzhou Engineer
若可变参数后面跟着命名关键字参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了。
1 def person(name, age, *args, city, job):
2 print(name, age, args, city, job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。而命名关键字参数可以有缺省值,从而简化调用:
1 def person(name, age, *, city='Hangzhou', job):
2 print(name, age, city, job)
由于命名关键字参数city具有默认值,调用时,可不传入city参数:
1 >>> person('Jack', 36, job='Engineer')
2 Jack 36 Hangzhou Engineer
6.参数组合
目前,函数中共有5种常用的参数类型。若只传入一种类型的参数,这太简单了。难点在哪?难点就在参数组合使用,那是相当恶心。不过,平时最好不要混合使用参数,不然容易搞得“乌烟瘴气”。
Python中,定义一个函数,我们可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
下面来定义一个函数,该函数参数包含一种或几种参数。
1 def f1(a, b, c=0, *args, **kw):
2 print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
3
4 def f2(a, b, c=0, *, d, **kw):
5 print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
可以调用上述函数:
1 >>> args = (1, 2, 3, 4)
2 >>> kw = {'d': 99, 'x': '#'}
3 >>> f1(*args, **kw)
4 a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
5 >>> args = (1, 2, 3)
6 >>> kw = {'d': 88, 'x': '#'}
7 >>> f2(*args, **kw)
8 a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
然而,虽然函数参数类型多达5种,但不要同时使用太多的组合,否则函数接口的可理解性很差。哎,简简单单才是真啊。
7.函数参数小结
参数,作为函数传入值的媒介,这里有必要做一个总结。
一、Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数;
二、默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误;
三、*args
是可变参数,args接收的是一个tuple;
四、**kw
是关键字参数,kw接收的是一个dict;
五、可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;
六、关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2});
七、使用*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法;
八、命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值;
九、定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*
,否则定义的将是位置参数。
故而,为了学好Python中的函数部分,参数不容忽视。
函数调用
在学习了函数的定义之后,我们应该需要调用函数,获取我们想要的数据。
如何调用函数呢?语法:函数名(参数)
Python中,大佬们内置了许多有点用的函数,而我们只需拿来就行。
若要调用Python中的内置函数,我们首先要知道函数名和参数。
比如我想要求某数的绝对值。如果你不知道Python有相关的内置函数,就只能这么做:
1 #求取绝对值
2 >>>def abs(num):
3 ... if num >= 0:
4 ... return num
5 ... else:
6 ... return (-num)
7 ...
8 >>>abs(9)
9 9
10 >>>abs(0)
11 0
12 >>>abs(-8)
13 8
上述代码虽然可以实现求绝对值的功能,但是太繁琐,需要敲几行代码才能实现该功能。然而,Python中有这个函数可以直接调用并输出结果。
1 #Python内置函数:abs()
2 >>>abs(-9)
3 9
4 >>>abs(9)
5 9
6 #获取帮助文档
7 >>>help(abs)
8 Help on built-in function abs in module builtins:
9
10 abs(x, /)
11 Return the absolute value of the argument.
对于函数参数,通常会遇到以下两个问题:
1.如果函数传入参数的数量错误,会如何呢?简单,直接Error呗。比如abs():
1 #函数传入参数的数量错误
2 >>> abs(-9,89)
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: abs() takes exactly one argument (2 given)
2.如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError
的错误,并且给出错误信息:str
是错误的参数类型:
1 #传入的参数类型错误
2 >>> abs('a')
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 TypeError: bad operand type for abs(): 'str'
常见内置函数(Built-in Functions)
Python 3.x版本下官方网站:https://docs.python.org/3/library/functions.html。该网址内显示Python内置函数相关内容(Built-in Functions)。
1.数据结构相关:list()、tuple()、dict()、str()……
2.数字相关:abs()、min()、max()、len()……
3.其他:int()、float()……
如果读者想知道图中函数的详细含义,请点击上述链接网址。调皮一下,这里就不附上网址啦~~~
数据类型转换
Python内置的常用函数还包括数据类型转换函数,比如int()
函数可以把其他数据类型转换为整数:
1 #Python之数据类型转换(int、float、str……)
2 >>> int('123')
3 123
4 >>> int(12.34)
5 12
6 >>> float('12.34')
7 12.34
8 >>> str(1.23)
9 '1.23'
10 >>> str(100)
11 '100'
12 >>> bool(1)
13 True
14 >>> bool('')
15 False
函数别名
把函数名赋给变量:
1 #函数“别名”
2 >>>abs(-8)
3 8
4 >>>a = abs
5 >>>a(-9)
6 9
7 >>>a(0)
8 0
9 >>>a(9)
10 9
递归函数
讲述递归函数之前,我想起一个东西:阶乘(n!)。举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n
,用函数fact(n)
表示,可以看出:
fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n
因此,递归函数:在函数内部,一个函数在内部调用自身本身。
于是,fact(n)用递归的方式写出来就是:
1 #递归函数
2 >>>def fact(n):
3 ... if n == 1:
4 ... return 1
5 ... else:
6 ... return fact(n - 1) * n
7 ...
8 >>>fact(1)
9 1
10 >>>fact(5)
11 120
12 #递归函数之栈溢出
13 >>>fact(1000)
14 Traceback (most recent call last):
15 File "<stdin>", line 1, in <module>
16 File "<stdin>", line 5, in x
17 File "<stdin>", line 5, in x
18 File "<stdin>", line 5, in x
19 [Previous line repeated 994 more times]
20 File "<stdin>", line 2, in x
21 RecursionError: maximum recursion depth exceeded in comparison
如代码所示,使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
下面来个斐波拉契数列:
1 #斐波拉契数列
2 >>>def fibo(arg1,arg2):
3 ... if arg1 == 0:
4 ... print(arg1,arg2)
5 ... arg3 = arg1 + arg2
6 ... print(arg3)
7 ... fibo(arg2, arg3)
8 ...
9 >>>fibo(0,1)
匿名函数
定义函数真得不可多得,但有时候不需要显示地定义函数。因此,函数也需要灵活地运用。使用匿名函数可以更加方便。
匿名函数语法:
lambda x: x * x(关键字lambda
表示匿名函数,冒号前面的x
表示函数参数)
1 def f(x):
2 return x * x
匿名函数的好处:因为函数没有名字,不必担心函数名冲突。
1 def is_odd(n):
2 return n % 2==1
3
4 L = list(filter(lambda n: n%2==1,range(1,20)))
5 print(L)
1.匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
2.Python中,匿名函数可以作为返回值返回并输出结果。