列表推导式
循环可以用来生成列表:
a = [x for x in range(4)]
a
[0, 1, 2, 3]
在循环的过程中使用 if
:
a = [x for x in range(3,10) if x % 2 == 0]
a
[4, 6, 8]
2 个for
循环:
a = [(x,y) for x in range(1,3) for y in range(1,3)]
a
[(1, 1), (1, 2), (2, 1), (2, 2)]
3 个 for
循环:
a = [(x,y,z) for x in range(1,3) for y in range(1,3) for z in range(1,3)]
a
[(1, 1, 1),
(1, 1, 2),
(1, 2, 1),
(1, 2, 2),
(2, 1, 1),
(2, 1, 2),
(2, 2, 1),
(2, 2, 2)]
也可以使用推导式生成集合和字典:
字典推导式:
values = [10, 21, 4, 7, 12]
square_dict = {x: x**2 for x in values if x <= 10}
print(square_dict)
{10: 100, 4: 16, 7: 49}
集合推导式:
values = [10, 21, 4, 7, 12]
square_set = {x**2 for x in values if x <= 10}
print(square_set)
{16, 49, 100}
编程练习
生成 1-200 之间所有能被3整除不能被5整除的数,请编写代码实现。
# 请编写你的答案
values = [i for i in range(1,201) if i%3==0 and i%5!=0]
print(values)
[3, 6, 9, 12, 18, 21, 24, 27, 33, 36, 39, 42, 48, 51, 54, 57, 63, 66, 69, 72, 78, 81, 84, 87, 93, 96, 99, 102, 108, 111, 114, 117, 123, 126, 129, 132, 138, 141, 144, 147, 153, 156, 159, 162, 168, 171, 174, 177, 183, 186, 189, 192, 198]
编程练习
请写出一段 Python 代码实现分组一个 list 里面的元素, 比如 [1,2,3,…100] 变成 [[1,2,3],[4,5,6]….],请编写代码实现。
# 请编写你的答案
values = [(i,i+1,i+2) for i in range(1,101,3)]
print(values)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15), (16, 17, 18), (19, 20, 21), (22, 23, 24), (25, 26, 27), (28, 29, 30), (31, 32, 33), (34, 35, 36), (37, 38, 39), (40, 41, 42), (43, 44, 45), (46, 47, 48), (49, 50, 51), (52, 53, 54), (55, 56, 57), (58, 59, 60), (61, 62, 63), (64, 65, 66), (67, 68, 69), (70, 71, 72), (73, 74, 75), (76, 77, 78), (79, 80, 81), (82, 83, 84), (85, 86, 87), (88, 89, 90), (91, 92, 93), (94, 95, 96), (97, 98, 99), (100, 101, 102)]
函数
定义函数
函数function
,通常接收输入参数,并有返回值。
它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
def add(x, y):
"""Add two numbers"""
a = x + y
return a
函数通常有一下几个特征:
- 使用
def
关键词来定义一个函数。 def
后面是函数的名称,括号中是函数的参数,不同的参数用,
隔开,def foo():
的形式是必须要有的,参数可以为空;- 使用缩进来划分函数的内容;
docstring
用"""
包含的字符串,用来解释函数的用途,可省略;return
返回特定的值,如果省略,返回None
。
使用函数
使用函数时,只需要将参数换成特定的值传给函数。
Python 并没有限定参数的类型,因此可以使用不同的参数类型:
print(add(2, 3))
print(add('foo', 'bar'))
5
foobar
在这个例子中,如果传入的两个参数不可以相加,那么 Python 会将报错:
print(add(2, "foo"))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_114/517506252.py in <module>
----> 1 print(add(2, "foo"))
/tmp/ipykernel_114/3800890303.py in add(x, y)
1 def add(x, y):
2 """Add two numbers"""
----> 3 a = x + y
4 return a
TypeError: unsupported operand type(s) for +: 'int' and 'str'
如果传入的参数数目与实际不符合,也会报错:
print(add(1, 2, 3))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_114/3888245736.py in <module>
----> 1 print(add(1, 2, 3))
TypeError: add() takes 2 positional arguments but 3 were given
传入参数时,Python 提供了两种选项,第一种是上面使用的按照位置传入参数,另一种则是使用关键词模式,显式地指定参数的值:
print(add(x=2, y=3))
print(add(y="foo", x="bar"))
5
barfoo
可以混合这两种模式:
print(add(2, y=3))
5
设定参数默认值
可以在函数定义的时候给参数设定默认值,例如:
def quad(x, a=1, b=0, c=0):
return a*x**2 + b*x + c
可以省略有默认值的参数:
print(quad(2.0))
4.0
可以修改参数的默认值:
print(quad(2.0, b=3))
10.0
print(quad(2.0, 2, c=4))
12.0
这里混合了位置和指定两种参数传入方式,第二个 2 是传给 a
的。
注意,在使用混合语法时,要注意不能给同一个值赋值多次,否则会报错,例如:
print(quad(2.0, 2, a=2))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/tmp/ipykernel_114/2505569231.py in <module>
----> 1 print(quad(2.0, 2, a=2))
TypeError: quad() got multiple values for argument 'a'
接收不定长参数
使用如下方法,可以使函数接受不定数目的参数:
def add(x, *args):
total = x
for arg in args:
total += arg
return total
这里,*args
表示参数数目不定,可以看成一个元组,把第一个参数后面的参数当作元组中的元素。
print(add(1, 2, 3, 4))
print(add(1, 2))
10
3
这样定义的函数不能使用关键词传入参数,要使用关键词,可以这样:
def add(x, **kwargs):
total = x
for arg, value in kwargs.items():
print("adding %s=%s"%(arg,value))
total += value
return total
这里, **kwargs
表示参数数目不定,相当于一个字典,键和值对应于键值对。
print(add(10, y=11, z=12, w=13))
adding y=11
adding z=12
adding w=13
46
再看这个例子,可以接收任意数目的位置参数和键值对参数:
def foo(*args, **kwargs):
print(args, kwargs)
foo(2, 3, x='bar', z=10)
(2, 3) {'x': 'bar', 'z': 10}
不过要按顺序传入参数,先传入位置参数 args
,再传入关键词参数 kwargs
。
返回多个值
函数可以返回多个值:
def divid(a, b):
"""
除法
:param a: number 被除数
:param b: number 除数
:return: 商和余数
"""
quotient = a // b
remainder = a % b
return quotient, remainder
quotient, remainder = divid(7,4)
print(quotient, remainder)
1 3
事实上,Python 将返回的两个值变成了元组:
print(divid(7,4))
(1, 3)
因为这个元组中有两个值,所以可以使用
quotient, remainder = divid(7,4)
给两个值赋值。
列表也有相似的功能:
a, b, c = [1, 2, 3]
print(a, b, c)
1 2 3
事实上,不仅仅返回值可以用元组表示,也可以将参数用元组以这种方式传入:
def divid(a, b):
"""
除法
:param a: number 被除数
:param b: number 除数
:return: 商和余数
"""
quotient = a // b
remainder = a % b
return quotient, remainder
z = (7,4)
print(divid(*z))
(1, 3)
这里的*
必不可少。
事实上,还可以通过字典传入参数来执行函数:
def divid(a, b):
"""
除法
:param a: number 被除数
:param b: number 除数
:return: 商和余数
"""
quotient = a // b
remainder = a % b
return quotient, remainder
z = {'a':7,'b':4}
print(divid(**z))
(1, 3)
map
方法生成序列
其用法为:
map(aFun, aSeq)
将函数 aFun
应用到序列 aSeq
上的每一个元素上,返回一个列表,不管这个序列原来是什么类型。
事实上,根据函数参数的多少,map
可以接受多组序列,将其对应的元素作为参数传入函数:
def divid(a, b):
"""
除法
:param a: number 被除数
:param b: number 除数
:return: 商和余数
"""
quotient = a // b
remainder = a % b
return quotient, remainder
a = (10, 6, 7)
b = [2, 5, 3]
print(list(map(divid,a,b)))
[(5, 0), (1, 1), (2, 1)]
编程练习
编写函数计算形式如 a + aa + aaa + … + aaa…aaa的表达式的值,其中 a 为小于 10 的自然数,请编写代码实现。
# 请编写你的答案
def genNum(x, n):
ans = 0
for i in range(n):
ans = ans * 10 + x
return ans
def addSum(x, n):
listX = [x] * n
nums = [i for i in range(1,n+1)]
ans = list(map(genNum,listX,nums))
print(ans)
return sum(ans)
addSum(2, 5)
[2, 22, 222, 2222, 22222]
24690
模块和包
模块
Python 会将所有 .py
结尾的文件认定为 Python 代码文件,考虑下面的脚本 ex1.py
:
%%writefile ex1.py
PI = 3.1416
def sum(lst):
"""
计算 lst 序列所有元素的和
:param lst: 序列 e.g. [1,2,3]
:return: lst 序列所有元素的总和
"""
# 获取 lst序列第一个元素
tot = lst[0]
# 循环遍历 lst 序列剩余元素
for value in lst[1:]:
tot = tot + value
return tot
w = [0, 1, 2, 3]
print(sum(w), PI)
Writing ex1.py
可以执行它:
%run ex1.py
6 3.1416
这个脚本可以当作一个模块,可以使用import
关键词加载并执行它(这里要求ex1.py
在当前工作目录):
import ex1
ex1
6 3.1416
<module 'ex1' from '/home/jovyan/work/1_math_and_programming_basic/ex1.py'>
在导入时,Python 会执行一遍模块中的所有内容。
ex1.py
中所有的变量都被载入了当前环境中,不过要使用
ex1.变量名
的方法来查看或者修改这些变量:
print(ex1.PI)
3.1416
ex1.PI = 3.141592653
print(ex1.PI)
3.141592653
还可以用
ex1.函数名
调用模块里面的函数:
print(ex1.sum([2, 3, 4]))
9
为了提高效率,Python 只会载入模块一次,已经载入的模块再次载入时,Python 并不会真正执行载入操作,哪怕模块的内容已经改变。
例如,这里重新导入 ex1
时,并不会执行 ex1.py
中的 print
语句:
import ex1
需要重新导入模块时,可以使用 reload
强制重新载入它,例如:
from imp import reload
reload(ex1)
6 3.1416
<module 'ex1' from '/home/jovyan/work/1_math_and_programming_basic/ex1.py'>
删除之前生成的文件:
import os
os.remove('ex1.py')
__name__
属性
有时候我们想将一个 .py
文件既当作脚本,又能当作模块用,这个时候可以使用 __name__
这个属性。
只有当文件被当作脚本执行的时候, __name__
的值才会是 '__main__'
,所以我们可以:
%%writefile ex2.py
PI = 3.1416
def sum(lst):
""" Sum the values in a list
"""
tot = 0
for value in lst:
tot = tot + value
return tot
def add(x, y):
" Add two values."
a = x + y
return a
def test():
w = [0,1,2,3]
assert(sum(w) == 6)
print('test passed.')
if __name__ == '__main__':
test()
Writing ex2.py
运行文件:
%run ex2.py
test passed.
当作模块导入, test()
不会执行:
import ex2
但是可以使用其中的变量:
ex2.PI
3.1416
引入模块时可以为它设置一个别名让使用更方便:
import ex2 as e2
e2.PI
3.1416
其它导入方法
可以从模块中导入变量:
from ex2 import add, PI
使用 from
后,可以直接使用 add
, PI
:
add(2, 3)
5
或者使用 *
导入所有变量:
from ex2 import *
add(3, 4.5)
7.5
这种导入方法不是很提倡,因为如果你不确定导入的都有哪些,可能覆盖一些已有的函数。
删除文件:
import os
os.remove('ex2.py')
包
假设我们有这样的一个文件夹:
foo/
__init__.py
bar.py
(defines func)baz.py
(defines zap)
这意味着 foo
是一个包,我们可以这样导入其中的内容:
from foo.bar import func
from foo.baz import zap
bar
和 baz
都是 foo
文件夹下的 .py
文件。
导入包要求:
- 文件夹
foo
在 Python 的搜索路径中 __init__.py
表示foo
是一个包,它可以是个空文件。
异常
写代码的时候,出现错误不可避免,即使代码语法没有问题,也可能遇到其它问题。
看下面这段代码:
import math
while True:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = math.log10(x)
print("log10({0}) = {1}".format(x, y))
这段代码接收命令行的输入,当输入为数字时,计算它的对数并输出,直到输入值为 q
为止。
乍看没什么问题,然而当我们输入 0 或者负数时:
import math
while True:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = math.log10(x)
print("log10({0}) = {1}".format(x, y))
> 0
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/tmp/ipykernel_114/1491084093.py in <module>
6 break
7 x = float(text)
----> 8 y = math.log10(x)
9 print("log10({0}) = {1}".format(x, y))
ValueError: math domain error
log10
函数会报错,因为不能接受非正值。
一旦报错,程序就会停止执行。如果不希望程序停止执行,并且想要捕捉异常,那么我们可以按照 try/except
语句。
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = math.log10(x)
print("log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
一旦 try
块中的内容出现了异常,那么 try
块后面的内容会被忽略,Python 会寻找 except
里面有没有对应的内容,如果找到,就执行对应的块,没有则抛出这个异常。
在上面的例子中,try
抛出的是 ValueError
,except
中有对应的内容,所以这个异常被 except
捕捉到。
程序可以继续执行:
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = math.log10(x)
print("log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
> 0
the value must be greater than 0
> 9
log10(9.0) = 0.9542425094393249
> -1
the value must be greater than 0
捕捉不同的错误类型
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
假设我们将这里的 y
更改为 1 / math.log10(x)
,此时输入 1
:
程序执行:
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
> 0
the value must be greater than 0
> 1
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
/tmp/ipykernel_114/230723649.py in <module>
7 break
8 x = float(text)
----> 9 y = 1 / math.log10(x)
10 print("log10({0}) = {1}".format(x, y))
11 except ValueError:
ZeroDivisionError: float division by zero
因为我们的 except
里面并没有 ZeroDivisionError
,所以会抛出这个异常,我们可以通过两种方式解决这个问题。
捕捉所有异常
将except
的值改成 Exception
类,来捕获所有的异常。
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1 / log10({0}) = {1}".format(x, y))
except Exception:
print("invalid value")
> 1
invalid value
> 4
1 / log10(4.0) = 1.660964047443681
指定特定异常
这里,我们把 ZeroDivisionError
加入 except
。
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1 / log10({0}) = {1}".format(x, y))
except (ValueError, ZeroDivisionError):
print("invalid value")
> 0
invalid value
> 1
invalid value
> q
或者另加处理:
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1 / log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
except ZeroDivisionError:
print("the value must not be 1")
> 1
the value must not be 1
> q
事实上,我们还可以将这两种方式结合起来,用 Exception
来捕捉其他的错误:
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1 / log10({0}) = {1}".format(x, y))
except ValueError:
print("the value must be greater than 0")
except ZeroDivisionError:
print("the value must not be 1")
except Exception:
print("unexpected error")
> 1
the value must not be 1
> 0
the value must be greater than 0
> q
得到异常的具体信息
在上面的例子中,当我们输入不能转换为浮点数的字符串时,它输出的是 the value must be greater than 0
,这并没有反映出实际情况。
为了得到异常的具体信息,我们将这个 ValueError
具体化:
import math
while True:
try:
text = input('> ')
if text[0] == 'q':
break
x = float(text)
y = 1 / math.log10(x)
print("1 / log10({0}) = {1}".format(x, y))
except ValueError as exc:
if exc.args[0] == "math domain error":
print("the value must be greater than 0")
else:
print("could not convert '%s' to float" % text)
except ZeroDivisionError:
print("the value must not be 1")
except Exception as exc:
print("unexpected error:", exc)
> a
could not convert 'a' to float
> q
同时,我们也将捕获的其他异常的信息显示出来。
这里,exc
显示的内容是异常对应的说明,例如
ValueError: could not convert string to float: a
对应的报错信息是
could not convert string to float: a
当我们使用 except Exception
时,会捕获所有的 Exception
和它派生出来的子类,但不是所有的异常都是从 Exception
类派生出来的,可能会出现一些不能捕获的情况,因此,更加一般的做法是使用这样的形式:
try:
pass
except:
pass
这样不指定异常的类型会捕获所有的异常,但是这样的形式并不推荐。
else
try/except
块有一个可选的关键词 else
。
如果使用这个子句,那么必须放在所有的 except 子句之后。else 子句将在 try 子句没有发生任何异常的时候执行。
try:
print(1)
except:
pass
else:
print('else was called.')
1
else was called.
出现异常,else 不会执行。
try:
print(1/0)
except ZeroDivisionError:
print('divide by 0.')
else:
print('else was called.')
divide by 0.
finally
try/except
块还有一个可选的关键词 finally
。
不管 try
块有没有异常, finally
块的内容总是会被执行,而且会在抛出异常前执行,因此可以用来作为安全保证,比如确保打开的文件被关闭。
try:
print(1)
finally:
print('finally was called.')
1
finally was called.
在抛出异常前执行:
try:
print(1 / 0)
finally:
print('finally was called.')
finally was called.
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
/tmp/ipykernel_63/3900254831.py in <module>
1 try:
----> 2 print(1 / 0)
3 finally:
4 print('finally was called.')
ZeroDivisionError: division by zero
如果异常被捕获了,在最后执行:
try:
print(1 / 0)
except ZeroDivisionError:
print('divide by 0.')
finally:
print('finally was called.')
divide by 0.
finally was called.
异常的处理流程可参考下图:
文档信息
- 本文作者:weownthenight
- 本文链接:https://weownthenight.github.io/2021/10/20/Python-%E8%BF%9B%E9%98%B6/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)