作者: 庄麟升, 张俊杰 研究助理
萨金特数量经济与金融研究所
北京大学汇丰商学院
Python 可以方便地做基本的计算。
在日常学习中,碰到了加法(+)、减法(-)、乘法(*)、除法(/)、取余数(%)、幂运算(**)这些基本的数学计算,聪明的你此刻应该会立刻灵光一闪,悄悄掏出珍藏在书包深处多年的计算器,然后背着老师狂按一通,接着喊出那响当当的运算结果:“老师我算出来了!”
恐怕,在这条名为“数学”的羊肠小路上,从小以来你最好的朋友就是那枚小小的计算器了,按一按计算器就可以算出结果,妈妈再也不用担心我的四则运算!不过,对于下面这个问题,你觉得按计算器一次成功的概率有多大?
$$ \frac{1}{\frac{2^{1/3}}{3} + 3\sqrt{5}} $$在计算器中,通常,你需要按以下这些按键:
1
/
(
2
^
(
1
/
3
)
/
3
+
3
*
5
^
0
.
5
)
)
别问我按对了没有,我不知道!……大不了再按一次比对一下嘛。那如果你又按错了呢?
现在,给你介绍一个新朋友,要不要?在 Python 中,要想解决这个问题,你需要的只是一行代码:(注意,在下面的代码中,**
代表幂 ^
)
1 / (2**(1/3) / 3 + 3*5**0.5)
好像也没有方便多少啊?
真的是这样吗?
使用程序来进行科学计算的好处至少有两点:
a = 2**(1/3) / 3
1 / (a + 3*5**0.5)
在上面的这段代码中,我们把中间步骤赋值给了一个变量 $a$,然后用这个变量去替换计算中对应的部分,快看快看,结果是不是一样的?
这样,如果我们想查看中间变量 $a$ 的具体值,就可以直接把它打印出来:
a
是不是很灵活?下面这张表总结了 Python 当中支持的算数运算符:
操作符 | 意义 | 例子 | 结果 |
---|---|---|---|
+ | 加法 | 1 + 1 | 2 |
- | 减法 | 2 - 1 | 1 |
* | 乘法 | 2 * 3 | 6 |
/ | 除法 | 6 / 3 | 2 |
% | 取余数 | 10 % 3 | 1 |
** | 幂运算 | 2 ** 3 | 8 |
在之前的例子中,我们已经看到了变量的作用——变量的运用使得程序具有了相当的灵活性。在计算中,如果想要使用一个变量,就直接给它起一个名字,然后通过等号=
赋一个值即可。一旦赋值完成,在之后的运算中我们就可以调用这个变量,就像前面的例子一样。
在本节,我们介绍 Python 中最重要的三类变量,它们分别是整数、浮点数和字符串。
整数是什么似乎就不用解释了,地球人都知道。所谓浮点数,这里可以粗略地理解为“带有小数点的数”——这样应该够容易理解了哈。那么,浮点数是不是指的就是“小数”呢?
不完全是。比如说 1.0
这个数在数学上应当被理解为整数而不是小数,但在 Python 中它是一个浮点数,因为它带有小数点。在下面这个例子中,我们给变量 $a$ 赋与 1.0 这个浮点数,给变量 $b$ 赋予 1 这个整数,则生成的两个新变量 $a$ 和 $b$ 的数值是一样的,但类型是不一样的。
相应,不带小数点的数则为整数。所以,Python 中的变量类型与数学上的数的类型不完全一样,但基本上是对应的。
a = 1.0
b = 1
a == b # 比较两个变量的大小,看看是不是相等
上面这段代码,除了一个奇怪的符号 ==
之外,你是不是都认识?这个==
符号是用来比较它左右两边的值是否相等的。如果相等,则==
运算的结果是True
(正确),如果不想等,它运算的结果则是 False
(错误).
我们看到,当我们判断浮点数 $a$ 与整数 $b$ 的大小是否相等的时候,Python 给了我们一个大大的回复:True(正确)。开不开心,意不意外,惊不惊喜?
除了 ==
用来判断是否相等之外,Python 还提供了一些其他的符号用来比较数值的大小:
print(a > b) # 判断 a 是否大于 b
print(a < b) # 判断 a 是否小于 b
print(a >= b) # 判断 a 是否大于等于 b
print(a <= b) # 判断 a 是否小于等于 b
这里,我们使用 Python 内置的 print
函数,将判断大小的计算结果打印了出来,结果是符合我们的认识的——由于 $a$ 与 $b$ 是一样大的,所以大于等于>=
和小于等于 <=
的运算输出了正确 (True) 的结果,而严格大于 >
与严格小于 <
则输出了错误 (False) 的结果。
前面的例子全是数啊,如果我想用一个变量表示我自己的名字,应该怎么办啊?我的名字不可能是 123 吧。
别着急,如果你的名字是 ”小明“,你也可以将一个变量赋值成 “小明”。这样,新生成的变量就是一个 字符串
类型的变量。
name = '小明'
print(name)
俩字符串类型的变量是不等比较谁大谁小的,但是可以比较是否相等。换言之,==
可以判断两个字符串是否完全一样。比说1号学生的名字叫“Tom”,2号学生的名字叫“Jerry”,我们将它们赋值给了两个变量 name_1
,name_2
name_1 = 'Tom'
name_2 = 'Jerry'
name_1 == name_2
如果你想改变一个变量的值怎么办?比如说你之前叫“小明”,你将你的名字赋值给了name
这个变量。后来,你觉得这个名字太土了,你决定改名为“小红”。该怎么办呢?
别怕,所谓“变量”的意思,指的不就是值可以变的量吗?所以,我们重新为变量name
赋一下值就行啦:
name = '小红'
print(name)
这一节开始,我们来介绍 Python 中的容器。所谓容器,就是指一个可以存放数据的仓库。Python 为我们提供了很多容器类型,但是我们这里只介绍一种最为基础也最为重要的容器类型:列表 (List)。
下面,我们来生成两个列表变量,list_1
与 list_2
:
list_1 = [1, 2, 'abc']
list_2 = [1, 2, 'abc']
print(list_1)
print(list_1 == list_2)
可以看到,list_1
与 list_2
两个列表的内容是一模一样的,它们的元素都是 1
2
'abc'
。因此,在使用 ==
运算符来判断它们是否相等时,Python 输出了正确 (True) 的结果。
这里,我们介绍四种常用的列表操作:(1)计算列表的长度,即列表中一共有多少元素;(2)在列表末尾添加元素;(3)在列表中间插入元素;(4)计算某元素在列表中出现的次数。
函数 | 操作 |
---|---|
len(list) | 计算列表的长度 |
list.append(obj) | 添加一个元素到列表的最后 |
list.insert(i,obj) | 在列表第 i 个位置插入一个元素 |
list.count(obj) | 计算元素 obj 在列表中的出现次数 |
首先,来看看我们的列表 list_1
有多长(即包含多少元素)。这里,我们使用 Python 内置的函数 len
:
len(list_1)
可以看到,列表 list_1
的长度为 3,即包含 3 个元素。
其次,如果想要在 list_1
这个列表后面增加一个字符串 'xyz'
,我们只需要在变量 list_1
后面加一个点 (.
),然后写上命令 append('xyz')
:
list_1.append('xyz')
list_1
接着,如果想要在列表开头插入一个字符串 'abc'
,则使用insert
函数,它的第一个参数是插入位置,第二个参数是插入的值。由于我们是要在列表开头插入元素,所以插入位置的索引值为 0:
list_1.insert(0, 'abc')
list_1
最后,看一看 'abc'
这个字符串在列表 list_1
中出现了多少次?
list_1.count('abc')
现在,我们已经得到了一个长度为 5 的列表 list_1
,它的元素分别为:'abc'
1
2
'abc'
, 'xyz'
。
如果我希望取出列表 list_1
中的第 3 个元素 2
,我应该怎么办呢?这涉及到 Python 中列表元素的索引方式。简单来说,代码应该这样写:
list_1[2]
即在列表名称 list_1
后面加上一个方括号 [ ] ,括号内填写上元素的编号 2
,以此来调用列表中编号为 2
的那个元素。我们来实验一下:
list_1[2]
成功了,我们成功从 list_1
中取出了元素 2
。聪明的你可能已经注意到一个问题了,明明元素 2
排在列表的第 3 位,可为什么它的编号是 2
呢?
很简单,因为 Python 中的元素是从 0
开始编号的啦~在下面这个图中,我们列出了元素编号与对应元素之间的关系。假设列表长度为 $n$,则 list_1[k]
取出的是第 k+1
个元素。
编号 | 对应元素 |
---|---|
0 | 第 1个元素 |
1 | 第 2个元素 |
... | ... |
n-1 | 最后一个元素 |
特别的,list_1[0]
取出的是列表的最开头的那个元素。这也就是为什么,在之前的 insert
函数中,我们要在最开头插入元素,则索引位置为 0 的原因。来试试看吧:
print( list_1[0] )
print( list_1[1] )
print( list_1[2] )
print( list_1[3] )
print( list_1[4] )
# print(list_1[5]) # 这行代码会出错,想想看为什么?
快看快看,我们是不是成功将 list_1
中的所有元素都取出来,并打印了出来?
现在问题来了,如果我想要使用 print
函数打印出 list_1
中的所有元素,像上面这段代码一样一个一个去取出 list_1
中的元素,岂不是很麻烦?如果 list_1
的长度比较小还好,可以如果某一天遇到一个长度为 10000 的列表,这样一个一个取值,要到猴年马月啊?
好消息,我们可以使用 for
循环来做这件事……
for x in list_1:
print(x)
哦耶,成功了!如果您的英语足够好的话,应该可以读懂以上这段代码的意思:
for x in list_1:
print(x)
翻译成中文就是:
对于 x 在 list_1 中 (意译就是:对于 list_1 中的任意 x):
打印(x)
循环的意思是不是够直白啦……上面这段代码的意思是:把 list_1
中所有的元素都遍历一遍,每经过一个元素的时候,就把它打印出来。
我们再学一个函数,叫做 range()
。
在 for
循环中,如果关键词 in
后面跟随的是 range(n)
,则 x
的取值从 0
递增到 n-1
。如果 in
后跟随的是 range(m, n, k)
,则 x
的取值分别为 m
m+k
m+2k
... 直到最后一个比 n
小的数为止;如果 k
省略,则默认 k
取 1。
例子 1:打印 3-7 之间的整数
for i in range(3, 8, 1):
print(i)
例子 2:我们可以使用循环来生成一个列表,列表元素为 0
10
20
30
40
50
60
new_list = []
for i in range(7):
new_list.append(i * 10)
new_list
当然了, 有更加简单的写法:
[i * 10 for i in range(7)]
或者:
list(range(0, 70, 10))
有一种特殊的语句,叫做条件语句,只有当条件成立的时候才执行某一段命令,条件不成立则不执行。这里给一个例子:将变量 $a$ 赋值为 3,接着用程序去判断 $a$ 是奇数还是偶数,如果是奇数,则打印 "a is odd" 这句话;反之,如果是偶数则打印 "a is even" 这句话:
a = 3
if a % 2 == 1:
print(a, 'is odd.')
if a % 2 == 0:
print(a, 'is even.')
在上面的这段语句中,使用了一个关键字 if
,如果学过一些简单的英语,就知道 if
是“如果”
意思,
现在考虑这样一个问题,在一个列表中存储着一些数据,我只想对其中的某些数据进行某种操作,而对其他数据进行另外的操作,这类问题应该怎么样去做呢?
这类问题,可以被归纳为一种常见的程序结构,我们在此可以称这种结构为“遍历结构”。它的形式如下:
for x in list: # 循环
if 条件 1: # if 判断
f(x)
elif 条件 2:
g(x)
elif 条件 3:
h(x)
else:
k(x)
上面这段伪代码除了关键字 if
以外,还出现了关键字 else
,它的意思是 “其他”
;此外,elif
表示在上一个“如果”不满足的情况下的这一个“如果”。所以,上面这段伪代码的含义很容易猜测出来:
list
中的每一个元素进行循环遍历,在每一次循环中,对于取出的那一个元素 x
,如果它满足条件1,则执行运算 f(x)
;如果不满足条件 1,但却满足条件 2,则执行 g(x)
运算;如果也不满足条件 2,但却满足条件 3,则执行 h(x)
运算;最后,如果条件 3 也不满足,则执行运算 k(x)
。这种遍历结构在程序设计中常常会出现,并且它足够强大,可以解决大量的实际问题,所以应该引起初学者足够的重视。
例子 (遍历结构):在0-9之中,将小于 3 的元素放在一个列表中,将大于等于 3 的奇数和偶数分别放在一个列表中。
less_3 = []
even_ge_3 = []
odd_ge_3 = []
for x in range(10):
if x < 3:
# 如果x小于3,x被放到容器less_3中
less_3.append(x)
elif x % 2 == 0:
#如果x大于3,而且除以2为0,即大于3的偶数,x被放到容器even_ge_3中
even_ge_3.append(x)
else:
#剩余其他的数都放到容器odd_ge_3中
odd_ge_3.append(x)
print(less_3)
print(odd_ge_3)
print(even_ge_3)
类似数学中的函数,给定一个输入值,就会有一个输出值, 在Python中,我们可以定义一个函数,然后多次调用。
例子 1:用 Python 写一个函数 $y = f(x) = 2x + 1$。
def f(x):
y = 2 * x + 1 # 函数的表达式
return y # 函数将Y的值返回给调用者
如上述代码所见,函数 $f$ 返回某个变量的时候,用到的 Python 关键字是 return
。
下面我们可以调用函数 $f(x)$ 来计算 $x=3$ 时的函数值 $y=f(3)$。聪明的你大概一眼就能够看穿吧:由于 $y = 2 \times 3 + 1$,所以 $y = 7$。我们来看看 Python 计算的结果如何:
f(3)
例子 2: $z = g(x, y) = x^2 + y^2 + xy$
def g(x, y):
z = x**2 + y**2 + x*y
return z
让我们来计算一下 $g(2, 3)$ 的函数值:
g(2, 3)
例子 3:编写一个函数,对于任意的整数输入值 $n$,计算 $1+2+...+n$ 的和:
def accumulation(n):
S = n
for i in range(n):
S = S + i
return S
测试当参数值 $n = 5$ 时,我们知道 $1+2+3+4+5 = 15$。
accumulation(5)
还记得当年数学王子高斯 (Guass, 1777-1855) 一战成名的那一到题是什么吗?嗯嗯,老师让还在读小学的小高斯计算从 1 加到 100 的和,高斯眨眨眼睛告诉老师,这道题太简单了,答案是 5050。笔者当年上小学的时候,数学老师也让我们算这道题,我记得当时在草稿纸上算了好几个小时呢,最后得到的结果是四千多(汗),恨死那个高斯了……不过你要是会编程,你也可以眨眨眼睛告诉老师,“这道题太简单了……”
accumulation(100)
例如: $e$,$\pi$, sin, cos, tan, log 函数
import math
math
工具箱里面有欧拉常数 $e$ 的值,我们可以直接拿来使用:
math.e
同样,math
工具箱里面还包含圆周率 $\pi$ 的值:
math.pi
例子 1:计算$$ sin\left(\frac{\pi}{6}\right)$$
math
工具箱中包含常见的三角函数,此处我们调用 sin 函数。由于计算机的表达能力有限,而 $\pi$ 是一个无穷小数,计算机只能近似表达,所以得到的结果也是近似等于 0.5。小学的时候,数学老师也只让用 3.14 来近似表示圆周率 $\pi$ 来进行各种计算,对吧?
math.sin(math.pi/6)
例子 2:计算 $$ ln(e)$$
math
工具箱中的 log
函数, 表示的正是以 $e$ 为底的对数函数。还记不记得,欧拉常数 $e$ 约等于 2.71828....
math.log(2.71828)
math.log(math.e)
例子 3:计算 $$ sin\left(\frac{\pi}{6}\right) + cos\left(\frac{\pi}{3}\right) $$
你是不是已经学聪明了?调用 math
工具箱是不是很方便啊。曾记否,$sin(\pi/6) + cos(\pi/3) = 1$,我们来看一看 Python 的运算结果:
math.sin(math.pi/6) + math.cos(math.pi/3)
下面语句的意思是从一个叫 matplotlib
的工具箱中导入一个小的工具箱 pyplot
,pyplot
可以帮助我们画图。
from matplotlib import pyplot
%matplotlib inline
想象一下我们如何在纸上画函数图呢,我们可以在纸上先画出足够多的点,然后把这些点连接起来,如果我们画的点足够的密集,图像就会变得“连续”。那么,如何才能得到足够多的点呢?我们要先找出足够多的 $x$ 值,然后根据函数表达式算出所有对应的函数值 $y$,在坐标轴上画出这些点,然后连接成一条线。
#导入数学工具箱中的 sin 函数和 pi 常数
from math import sin, pi
# 先准备两个容器,x 容器放横坐标的值,y 容器放对应的纵坐标的值
x_list = []
y_list = []
for i in range(200):
# range(200) 会生成一个包含0-199的数列, 也就是说我们选取200个点来进行画图
# for 循环是对容器中的每个值,进行处理一次
# 首先,对于横坐标,把1-199,缩小100倍, 这只是缩小比例尺,便于画图显示
x_point = 0.01*i
# 再次,将缩小的横坐标值放回x容器中
x_list.append(x_point)
# 根据每一个横坐标值,计算对应的纵坐标值,对应的函数是:sin(x*pi)**2
y_point = sin(pi*x_point)**2
y_list.append(y_point)
x_list
中是所有的横坐标的值,总共有 200 个,构成一个列表。此处我们将这个长度为 200 的列表打印出来,显示的时候四舍五入到小数点后两位:
print([round(x, 2) for x in x_list])
同样,y_list
是所有纵坐标的值,也是一个长度为 200 的列表。
print([round(y, 2) for y in y_list])
最后,我们掉用我们之前导入的画图 pyplot
工具,画出所有的点,由于点很密集,所以当把这些点逐个连接起来的时候,显示的线是光滑的曲线,而不是一条条折线。
pyplot.plot(x_list, y_list)
pyplot.show()
更多Python编程知识,可以参考:Python基础知识