2.1.编程要素

编程语言不仅是一种指挥计算机执行任务的手段,它应该成为一种框架,使我们能够在其中组织我们的有关计算过程的思想。

同时,程序也在编程社区中传递想法,所以,编程语言必须是人类可以阅读的并且恰巧可以被机器执行


2.1.1表达式

上一节中我们解释的Python的解释器,下面我们将重新开始,一步步讲解Python语言

我们先从最基本的表达式开始 数字 number

>>> 42
42
>>> pi
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    pi
NameError: name 'pi' is not defined
>>> from math import pi
>>> pi
3.141592653589793

在上面这个例子上我们发现,Python认识我们输入的数字,在导入库后也认识了一些常数,而这些基本表达式可以在一些操作符和运算符之间进行运算

2.1.2.调用表达式

我们可以将一些表达式与变量调用到函数的参数上,在Python中函数的应用方式与传统数学相同

>>> max(pi,(14/3+5*0.13)/2*pi)
8.351400470792868
>>> from math import sin
>>> sin(pi/2)
1.0

在这个例子中我们展示了两个函数 maxsin

  • max 函数,他接受两个变量并且支持接受表达式
  • sin 函数,他只接受一个表达式
    这两个例子都在说明我们调用表达式的时候,可以在表达式内添加一个子表达式

在调用表达式的时候,参数的顺序也是很重要的,比如当我们调用 pow 函数的时候,它的第二个参数是第一个参数的幂

>>> pow(2,3)
8
>>> pow(2,3,4)
0

注意到第四行奇怪的地方了吗?第一眼看上去可能认为这是和C++一样的 函数重载 但是其实并非这么一回事

Python同名函数传参 这里我们会详细探讨一下这种情况

2.1.3.导入库函数

在Python中实际上定义了大量的函数,但是我们不能直接使用他们,我们需要导入他们的库函数才能实现对他们的调用。

例如在math中提供了许多熟悉的数学函数

>>> from math import sqrt
>>> sqrt(256)
16.0

improt 语句需要指定模块名称(例如 mathoperator),然后列出要导入这个模块的具体名称

当我们尝试将整个库都导入时候,我们就可以直接输入 import [库名] ,在我们调用这个库中的函数的时候就需要在函数名字之前添加库的名称 math.pow()
我们也可以使用 as 语法为库名添加别名

import operator as op
op.add(1,2) # same as a + b

2.1.4.名称与环境

编程语言的一个要素就是使用名称来引用计算对象,如果一个值被赋予了名称,就说名称被绑定到了这个值上

在Python中,我们使用赋值语句来建立新的绑定,即 = 左边的是名称右边的是值

>>> radius = 10
>>> radius
10
>>> 2 * radius
20

在Python中 = 被称为 赋值 符号,与C++不同的是,C++属于强类型语言, = 两边的类型至少应该相似,但是python具有多态特性, = 是 Python里最简单的 抽象 方法,在此我们不讨论过多,感兴趣可以看看我写的 语言类型与转换

同时,将名称与值绑定,之后通过名称检索可能的值,就意味着解释器必须维护某种内存来记录名称、值和绑定,这种内存就是环境

你甚至可以将名称与函数绑定

>>> max
<built-in function max>
>>> f = max
>>> f
<built-in function max>
>>> f(1,2,5)
5
>>> max = 7
>>> max
7
>>> f(1,2,max)
7
>>> max(1,2)
Traceback (most recent call last):
  File "<python-input-12>", line 1, in <module>
    max(1,2)
    ~~~^^^^^
TypeError: 'int' object is not callable
>>> max = f
>>> max(1,2)
2

在上面的例子中我们发现,不仅可以将 f 赋值为 max ,也可以将 max 变成其他东西,但是当我们将 max 变为其他东西的时候, max 就失去了作为函数的能力

与C++类似的是,Python在执行赋值语句的时候会先求解右侧的表达式,再将结果与左侧的名称绑定

对于多重赋值,所有 = 右边的表达式都会先求值,然后再与左边的名称绑定。

>>> x,y = 3,4.5
>>> y,x = x,y
>>> x
4.5
>>> y
3

Python中还有一种允许我们创建名称的方法,即 创建新函数

>>> def name(参数):
...     return 返回值

例如:

>>> def square(x):
...     return x*x
...
>>> square
<function square at 0x7ca186cd2840>
>>> square(7)
49
>>> square(7+9)
256
>>> square(square(3))
81

这就是自定函数的操作

我们也可以将自定函数写入另外一个自定函数中,同时,我们也可以构造一个无参数的函数用于返回值

>>> r = 10
>>> from math import pi
>>> def area():
...     return pi * r * r
...
>>> area()
314.1592653589793
>>> r = 20
>>> area()
1256.6370614359173

我们发现在上述过程中我们更新 r 的值后重新调用 area() 函数也会更新

下面我们来深入讲解一下定义函数

2.2.定义函数

定义函数是我们在学习和使用Python中最重要的一件事情,这是一种更加强大的抽象方式,将一系列简单或复杂的事物抽象成一个函数名使我们在调用的时候无需关系具体的实现过程

定义一个新函数的语法是

def <name>(<formal parameters>)
	return <return expression>

第一行中 <name> ... 被称为函数声明,他定义了函数名称以及需要调用的参数
第二行中 return ... 告诉函数应该返回什么

对于函数,在我们编写函数整体的时候,实际上没有进行任何操作,我们只是将规则编写下来并且保存到我们的环境中,只有真正调用的时候才会执行函数中编写的内容,这一点上与 C++ 是类似的


2.2.1 环境

虽然现在我们对Python的了解已经足够复杂,但是程序的意义并不是很明显。如果特定形参与内部函数同名那么我们应该怎么办?

在Python中,求解表达式的环境由构成,每个帧都包含了一些 绑定全局帧只有一个。同时,赋值和导入语句会将条目添加到当前环境的第一帧

下面有一段 html 嵌入代码向我们展示了可视化的栈帧生成以及返回的流程,我们也可以在Online Python Tutor 自己尝试

我们可以发现这个环境图上 `Global frame` 储存了两个变量,其都是存储在全局的栈帧上的,而当我们导入函数或者创建函数后都会有新的对象出现,示例如下

现在我们可以给出一个递归的例子来辅助我们了解栈帧这一概念

我们主要这个函数 f ,它在其函数体内调用了本身,构成了一个 自己调用自己 的场景

  • 每当函数 f 调用新的函数 f 时候,都会额外开出一个栈帧用来进行新函数 f 的运行
  • 当这个 f 运行结束返回值的时候,这个返回值会在这个栈里自下而上的进行直到回到全局栈帧

2.2.2 调用用户定义的函数