编程的要素
编程语言不仅仅是用来指示计算机完成任务的工具。它不仅有助于计算机执行特定的操作,还在更深层次上起到了其他重要作用。编程语言作为一个框架,帮助我们组织和表达关于计算过程的思想。通过编程语言,我们能够系统地思考和设计计算流程和算法。而程序是编程社区成员之间交流思想的媒介。程序不仅用于实现功能,还用于传达设计理念、解决问题的方法以及逻辑思路。因此,程序应该首先是为人阅读而写的,只有在此基础上才是为机器执行而写的。这意味着代码的可读性和清晰性是至关重要的,因为它们直接影响到程序员之间的交流和合作。
当我们描述一种语言时,我们应特别关注语言提供的用于将简单的思想组合成更复杂的方法。每种强大的语言都有三种这样的机制: 每一种功能强大的语言都有三种实现这一目的的机制:
- 原始表达式(primitive expressions and statements),代表着语言所关注的最简单的实体。
- 组合的方法(means of combination),通过一系列的组合,将复合元素从简单元素构建。
- 抽象的方法(means of abstraction),即给复合元素命名并把它们作为单元来操作。
在编程中,我们处理两类元素:函数和数据。这是编程的基础,所有操作都围绕这两者展开。不过很快我们会发现,函数和数据实际上并没有那么不同。函数可以作为数据处理,而数据也可以通过函数进行转化非正式地说,数据是我们想要操作的东西,而函数则描述了操作数据的规则。数据是操作的对象,函数是执行操作的工具。因此,任何强大的编程语言都应该能够描述原始数据和原始函数,并具备一些方法来组合和抽象函数和数据。一门编程语言不仅要能够处理基本的数据类型和函数,还要能够提供机制来组合这些基本元素,形成更复杂的结构和操作。
在本章中,我们将通过 python 处理简单的数值数据,这样我们就可以专注于构建程序的规则。在后面的章节中,我们将看到这些相同的规则使我们能够构建用于操作复合数据的程序。
原始表达式
原始表达式(Primitive Expressions)是编程语言中最基本的构造块。它们通常包括数字、字符串、布尔值等。这些表达式是计算过程的基本单位,不需要进一步的解释或简化。例如: 数字:42, -3.14 字符串:"Hello, World!" 布尔值:true, false 这些原始表达式可以直接用于计算或作为更复杂表达式的一部分。 我们以数字为例,在 Python 解释器中,我们可以对数字进行基本的操作。
>>> 114514
114514
2
表达式可以和数学运算符结合,形成复合表达式,我们的 python 解释器会对其进行求值.
>>> 114514+1919810
2034324
>>> 11.14514-19.19810
-8.05296
2
3
4
调用表达式
最重要的一种复合表达式是调用表达式,它将函数应用于某些参数。回顾一下高中数学,函数的数学概念是从一些输入参数到输出值的映射。例如,max 函数将其输入映射到单个输出,即最大的输入。Python 表达函数应用的方式与传统数学相同。
>>> max(114514, 1919810)
1919810
2
与我们在长久以来的数学学习中使用的中缀表示法相比,函数符号有三个主要优点。首先,函数可以接受任意数量的参数:不会产生歧义,因为函数名总是在参数之前。
其次,函数符号可以直接扩展到嵌套表达式,其中的元素本身就是复合表达式。在嵌套的调用表达式中,与复合中缀表达式不同,嵌套的结构在括号中是完全显式的。
>>> max(min(11,45),pow(max(14,19),1),max(98,10))
98
2
然而,人类很快就会被多层嵌套搞糊涂。作为程序员的一个重要任务就是构建表达式的结构,使其能够被你自己、你的编程伙伴以及将来可能阅读你的代码的其他人所理解。 第三,数学符号的形式多种多样:乘法出现在项与项之间,指数以上标形式出现,除法以横杠形式出现,平方根以斜屋顶形式出现。有些符号很难输入!然而,所有这些复杂的符号都可以通过调用表达式来统一。虽然 Python 支持使用中缀符号的常用数学运算符(如 + 和 -),但任何运算符都可以用函数名来表示。
调用包中的函数
在编程中,我们经常需要使用外部包或库中的函数。为了调用包中的函数,首先需要导入包。然后,可以使用包提供的函数。例如,在 Python 中使用 math 包中的 sqrt 函数:
>>> import math
>>> result = math.sqrt(16)
>>> result
4.0
2
3
4
(调个包能有多难) Python3 的库文档列出了每个包定义的函数,如 math 模块,request 模块。每次你想调库函数的时候建议去查文档,而不是通过先尝试使用来了解它的行为。
命名与环境
编程语言的很重要的一点是它提供了使用名称来指代计算对象的方法。如果一个值被赋予一个名称,我们就说该名称与该值绑定。 在 Python 中,我们可以使用赋值语句建立新的绑定,该语句在等号的左边包含一个名称,在右边包含一个值:
>>> frog = "Suwako"
>>> frog
'Suwako'
>>> 'Moriya ' + frog
'Moriya Suwako'
2
3
4
5
等号(=)符号在 Python(以及许多其他语言)中被称为赋值运算符。赋值是我们最简单的抽象手段,因为它允许我们使用简单的名字来引用复合操作的结果,例如上面计算的面积。通过这种方式,复杂的程序是通过逐步构建越来越复杂的计算对象而构建的。 将名称绑定到值并在以后通过名称检索这些值的可能性意味着解释器必须维护某种内存来跟踪名称、值和绑定。这种内存被称为环境(environment)。名称也可以绑定到函数。例如,名称 max 绑定到我们一直在使用的 max 函数。与数字不同,函数很难用文本呈现,因此当要求描述一个函数时,Python 会打印一个标识性描述来代替:
def square(x):
return x * x
print(square)
# Output: <function square at 0x7f8c8c1b2d30>
2
3
4
5
在这个例子中,square 是一个函数,当打印它时,Python 输出了一个函数的标识性描述,而不是函数的内容或结果。这表明 square 这个名字已经绑定到一个具体的函数对象上。
嵌套表达式的求值
我们本章的目标之一是隔离关于过程化思维的问题。作为一个例子,考虑在评估嵌套调用表达式时,解释器本身是遵循一个过程的。
要评估一个调用表达式,Python 将执行以下步骤:
评估操作符和操作数子表达式,然后 将操作符子表达式的值(即函数)应用于操作数子表达式的值(即参数)。 即使这个简单的过程也说明了一些关于过程的重要点。第一步规定,为了完成对调用表达式的评估,我们必须首先评估其他表达式。因此,评估过程是递归的;也就是说,它包括作为其步骤之一的对自身规则的调用。
例如,评估:
>>> sub(pow(2, add(1, 10)), pow(2, 5))
2016
2
需要应用这个评估过程四次。我们画出每个评估的表达式来可视化这个过程的层次结构。这种图示被称为表达式树。在计算机科学中,树通常从上向下生长。树中每个点的对象称为节点;在这里,它们是表达式及其值的配对。 评估树的根,即顶部的完整表达式,首先需要评估作为其子表达式的分支。叶子表达式(即没有分支的节点)代表函数或数字。内部节点有两个部分:应用我们的评估规则的调用表达式和该表达式的结果。从树的终端节点开始,操作数的值向上渗透,然后在更高的层次上组合。 接下来,观察第一步的反复应用使我们需要评估的不是调用表达式,而是原始表达式,例如数字(如 2)和名称(如 add)。我们通过规定以下内容来处理这些原始情况:
- 数字表达式的评估结果是它表示的数字。
- 名称表达式的评估结果是当前环境中与该名称关联的值。
- 注意环境在确定表达式中符号含义时的重要作用。在 Python 中,如果没有关于提供名称 x(甚至是名称 add)含义的环境信息,讨论表达式的值是没有意义的:
>>> add(x, 1)
环境提供了评估发生的上下文,这在理解程序执行时起着重要作用。 这个评估过程不足以评估所有 Python 代码,仅限于调用表达式、数字和名称。例如,它不处理赋值语句。执行:
>>> x = 3
不会返回值或评估函数的某些参数,因为赋值的目的是将名称绑定到一个值。一般来说,语句不是被评估而是被执行;它们不产生值,而是进行某些更改。每种表达式或语句类型都有其自己的评估或执行过程。 需要注意的是,当我们说 “一个数字评估为一个数字” 时,我们实际上是指 Python 解释器将数字评估为一个数字。是解释器赋予编程语言意义。鉴于解释器是一个始终行为一致的固定程序,我们可以说在 Python 程序的上下文中,数字(和表达式)本身评估为值。
PS:(嗯这章翻译的比较粗糙,大家可以去这里看看原文。)