Chapter 1: Building Abstractions with Functions

1.1 Getting Started 1.2 Elements of Programming 1.3 Defining New Functions 1.4 Designing Functions 3.3 Exceptions


1.1 重点摘要

“Functions encapsulate logic that manipulates data.”

函数封装了用于处理数据的逻辑。

“The process by which the former leads to the latter may be complex, but we can apply that process using only a simple expression because that complexity is tucked away within a function.”

从前一种数据得到后一种数据的过程可能很复杂,但我们只需使用一个简单表达式就能应用这个过程,因为复杂性被隐藏在函数内部。

“A set is a type of object, one that supports set operations like computing intersections and membership.”

集合是一种对象,它支持集合操作,例如计算交集和判断成员是否存在。

“An object seamlessly bundles together data and the logic that manipulates that data, in a way that manages the complexity of both.”

对象将数据和操作这些数据的逻辑无缝地组合在一起,并以此管理两者的复杂性。

“In the end, we will find that all of these core concepts are closely related: functions are objects, objects are functions, and interpreters are instances of both.”

最终我们会发现,这些核心概念之间密切相关:函数是对象,对象是函数,而解释器则同时是两者的实例。

“All computing begins with representing information, specifying logic to process it, and designing abstractions that manage the complexity of that logic.”

所有计算都始于:表示信息、规定处理信息的逻辑,以及设计用于管理这些逻辑复杂度的抽象。

这句话可以看作整本书的总纲:数据表示、处理逻辑、抽象管理复杂度。

“Python code consists of expressions and statements.”

Python 代码由表达式和语句组成。

“Statements typically describe actions. When the Python interpreter executes a statement, it carries out the corresponding action.”

语句通常描述动作。Python 解释器执行一条语句时,会完成相应的动作。

“Expressions typically describe computations. When Python evaluates an expression, it computes the value of that expression.”

表达式通常描述计算。Python 对一个表达式求值时,会计算出该表达式的值。

你可以将这组概念简记为:

# 表达式:重点是得到一个值
2 + 2
len("Python")

# 语句:重点是执行一个动作
name = "Ada"
print(name)

“A program that implements such a procedure, evaluating compound expressions, is called an interpreter.”

实现这种求值过程、能够计算复合表达式的程序,称为解释器。

“Programming is about a person using their real insight to build something useful, constructed out of these teeny, simple little operations that the computer can do.”

编程是人利用自己的洞察力,把计算机能够执行的许多微小、简单操作组合起来,构建出有用的东西。

“Learning to interpret errors and diagnose the cause of unexpected errors is called debugging.”

学习理解错误信息、诊断意外错误的原因,称为调试。

“Test incrementally: Every well-written program is composed of small, modular components that can be tested individually.”

增量测试: 每个写得好的程序都由小型、模块化、可以单独测试的组件构成。

“Isolate errors: An error in the output of a statement can typically be attributed to a particular modular component.”

隔离错误: 一条语句输出中的错误,通常可以归因到某个特定的模块化组件。

“Check your assumptions: Interpreters do carry out your instructions to the letter — no more and no less.”

检查假设: 解释器会严格执行你的指令,不多也不少。

“Consult others: You are not alone!”

向他人求助:你并不孤单。

1.1 个人理解与感悟

在学习本章之前,我对编程中一些基础术语的理解并不清晰。学习之后,我开始意识到:许多看似相近的概念,其实关注的是程序运行过程中的不同方面。本章中,我认为最重要的收获主要有两点。

一、Expression(表达式)与 Statement(语句)的区别

最开始我不清楚表达式和语句分别指什么,甚至以为它们只是同一类代码的不同说法。学习之后可以发现,它们的作用不同:

Expression(表达式) 表达式会被 Python 求值,并产生一个值。这个值可以是数字、字符串、布尔值、列表,也可以是更复杂的对象。

2 + 3
len("hello")
x > 10

上面的代码都是表达式:它们分别可以求值得到 5、5 和 True 或 False。

Statement(语句) 语句由 Python 执行,其重点是让程序完成某种动作,例如绑定名称、改变程序状态、控制程序流程、定义函数或导入模块。

x = 10
if x > 0:
    print("x 是正数")

其中,x = 10 是赋值语句;if 是条件语句。它们的主要目的不是作为一个值参与其他计算,而是组织或改变程序的执行。

简单来说,表达式的核心是“求值并得到一个值”,语句的核心是“执行动作、改变状态或控制流程”。一条语句中常常会包含表达式。例如:

total = price * quantity + shipping_fee

整行是赋值语句,而右侧的 price * quantity + shipping_fee 是表达式。Python 会先计算右侧表达式的值,再把结果绑定给 total。

二、函数与对象的关系

“面向对象编程”中的“对象”常常容易让初学者感到抽象。我以前也没有清楚地区分函数与对象。现在可以先从 Python 的角度,将它们理解为“包含关系”,而不是互相对立的概念。

函数是对象的一种,而对象不一定是函数 在 Python 中,函数本身是对象;它可以被赋值给变量、作为参数传递、作为函数返回值,也可以具有属性。函数的特殊之处在于,它通常用于封装某个计算过程或动作,并且可以被调用。

def add(a, b):
    return a + b

result = add(2, 3)

在这里,add 是一个函数对象;而 add(2, 3) 是对该函数对象进行调用所形成的表达式,其结果为 5。

对象是 Python 运行时可以操作的具体值或实体 在 Python 中,整数、字符串、列表、字典、函数、类,以及根据类创建的实例,都是对象。它们有不同的类型、不同的行为,也可能具有不同的数据或状态。

number = 10
text = "Python"
items = [1, 2, 3]

这里的 10、"Python" 和 [1, 2, 3] 都是对象;number、text 和 items 是用来引用这些对象的名称。

不过,不应把所有与编程有关的内容都称为对象。例如,变量名、if、def 等关键字,以及运算符和语法规则,本身并不等同于运行时对象。

可调用性(callable)是一种能力,而不是对象的定义 函数最显著的特征是可以被调用:

add(2, 3)

但在 Python 中,类也通常可以被调用以创建实例;某些实例也可以通过定义 __call__ 方法而表现得像函数。因此,“能不能调用”不能用于判断一个东西是否为对象,而只能说明它是否具有“可调用”这一能力。(此处的类和示例在第一章节并没有提到,所以此处只是引用了 AI 的回复,后续再继续学习和解答。)

number = 10
# number()  # 整数对象通常不可调用

def greet(name):
    return f"你好,{name}"

greet("小明")  # 函数对象可调用

总结

总的来说,可以暂时将对象理解为:

对象是在程序运行时可以被引用、具有类型、值或状态,并能按照其类型规则被操作的具体实体。

从这个角度看,对象的范围很广;函数则是对象中的一种特殊形式,重点在于封装行为并支持调用。

1.2 重点摘要

“A programming language is more than just a means for instructing a computer to perform tasks. The language also serves as a framework within which we organize our ideas about computational processes.”

编程语言不仅仅是指导计算机执行任务的手段。它还充当了一个框架,让我们能够在其中组织关于计算过程的构思。

“Every powerful language has three such mechanisms: primitive expressions and statements, by which compound elements are built from simpler ones; means of combination, by which compound elements are built from simpler ones; and means of abstraction, by which compound elements can be named and manipulated as units.”

每种强大的语言都具备三种机制:基本表达式和语句,用于从简单的元素构建复合元素;组合手段,用于从简单的元素构建复合元素;以及抽象手段,使得复合元素可以被命名并作为单元进行操作。

“Informally, data is stuff that we want to manipulate, and functions describe the rules for manipulating the data.”

通俗地说,数据是我们想要操作的对象,而函数则描述了操作这些数据的规则。

“If a value has been given a name, we say that the name binds to the value.”

如果一个值被赋予了一个名称,我们就说该名称绑定到了这个值上。

“The possibility of binding names to values and later retrieving those values by name means that the interpreter must maintain some sort of memory that keeps track of the names, values, and bindings. This memory is called an environment.”

能够将名称绑定到值,并随后通过名称检索这些值,意味着解释器必须维护某种内存来跟踪这些名称、值和绑定。这种内存被称为环境。

“In general, statements are not evaluated but executed; they do not produce a value but instead make some change.”

通常情况下,语句不是被求值,而是被执行;它们不会产生一个值,而是会产生某种改变。

“Pure functions have the property that applying them has no effects beyond returning a value. Moreover, a pure function must always return the same value when called twice with the same arguments.”

纯函数具有这样的特性:应用它们除了返回一个值之外,不会产生任何其他影响。此外,当用相同的参数调用两次时,纯函数必须始终返回相同的值。

“In addition to returning a value, applying a non-pure function can generate side effects, which make some change to the state of the interpreter or computer.”

除了返回一个值之外,应用非纯函数还会产生副作用,即对解释器或计算机的状态产生某种改变。

1.2 个人理解与感悟

编程语言的本质不仅仅是指令集,它更是组织和表达计算思想的思维框架。正如那句名言所说:Programs must be written for people to read and only incidentally for mechanisms to execute.程序的核心受众是人类开发社区,因此编写代码必须严格遵守团队和社区规范,以保证代码的可读性和可维护性,不能随心所欲。

任何强大的编程语言都提供了将简单思想组合成复杂思想的三种手段:

  1. 原始构建块:语言提供最基本的元素,如数值或内置名称。
  2. 组合手段:将简单元素拼接成复合元素,例如通过运算符进行加减乘除,或调用表达式组成复合运算。
  3. 抽象手段:为了避免重复编写复杂的组合逻辑,我们需要将复合元素命名,并将其作为整体进行操作(例如通过赋值语句将复杂的计算过程抽象成简单的变量名)。

数据与函数的关系在于:数据是我们想要操纵和处理的原始信息,而函数是操纵数据的规则和方法。

关于 Python 中的名称、绑定与环境: • 绑定:当一个值被赋予变量名时,该名称就被绑定到该值上。 • 环境:即名称到值的映射表,是解释器维护的一张记忆表。 • 规则:在同一个环境中,一个名称在同一时刻只能绑定一个值。如果赋予新值,旧值会自动解绑;反之,同一个值可以被不同的名称同时绑定(例如 A、B、C 同时等于 32,它们指向的是同一个值)。 • 内置名称:在 Python 中,内置名称(如 max)是可以被重新绑定的,但一般不建议这样做,否则可能导致原有的内置函数失效。

表达式(Expression)与语句(Statement)的底层差异: • 表达式:核心任务是以数据输出为导向的计算过程。底层原理是利用 CPU 的 ALU(算术逻辑单元)进行运算,结果必然会保存在 CPU 寄存器或数据栈顶,等待后续指令消费。执行方式是被求值(Evaluated),一定会产生并返回一个值。 • 语句:核心任务是改变状态或控制流程。底层行为是通过修改内存或跳转程序计数器来驱动程序,执行完毕后清空数据栈,不留下任何值。执行方式是被执行(Executed),通常不返回值。

关于函数与表达式的几个经典困惑:

  1. 函数定义(Function Definition):不返回值,作用是在环境中创建名称绑定,因此属于语句。
  2. 函数调用(Call Expression):它会返回一个值(没有显式返回值时默认返回 None),因此属于表达式,且支持嵌套使用。
  3. 函数本身(Function Object):属于值或数据,因为在 Python 中它可以像普通数值一样被赋值给变量或传递。
  4. 带副作用的表达式(Non-pure Expression):如果一个结构在改变状态的同时输出了值,它就属于带副作用的表达式。
  5. 纯表达式(Pure Expression):不带副作用,纯粹接受输入值并输出计算结果。其特性是:当你二次调用(Call Expression)时,给予相同的参数(Arguments),它一定能输出同样的数据或值。

在不同的语言中,函数和表达式的界限有所不同,例如 Python 中界限较清晰,而在某些语言中一切皆可为表达式,这一点需要留意。

前面我们经常提到“值”或者“数据”这两个表达。在通常情况下,我们直接把值等同于数据是没有问题的,在日常口语表达或交流中,完全可以把它们当成指向同一事物的同义词。

但如果从严格意义上来讲:

  1. 数据:是我们最开始、最原始想要操纵的对象。
  2. 表达式:是对数据进行加工和演算的逻辑流水线。
  3. 值:是经过表达式的逻辑和计算之后,被加工出来的、我们想要的终端结果。 所以说,数据和值其实是同一个事物在不同状态下的称呼,因为一个值被计算出来之后,很有可能会成为下一阶段的数据。

1.3 重点摘要

The return expression is not evaluated right away; it is stored as part of the newly defined function and evaluated only when the function is eventually applied.

return 表达式不会立即求值;它会被存储为新定义的函数的一部分,仅在函数最终被调用时才进行求值。

Each instance of a function application has its own independent local frame.

函数调用的每个实例都有其自己独立的局部帧。

A name evaluates to the value associated with that name in the earliest frame of the current environment in which that name is found.

名称求值的结果是当前环境中能够找到该名称的最早帧中所关联的值。

The scope of a local name is limited to the body of the user-defined function that defines it.

局部名称的作用域仅限于定义它的用户自定义函数的函数体。

A programmer should not need to know how a function is implemented in order to use it.

程序员在使用函数时,无需了解该函数是如何实现的。

The domain of a function is the set of arguments it can take.

函数的定义域是它所能接受的参数集合。

The range is the set of values it can return.

值域是它所能返回的值的集合。

Python expressions with infix operators … can often be thought of as short-hand for call expressions.

带有中缀运算符的 Python 表达式……通常可以被视为调用表达式的简写形式。

1.3 个人理解与感悟

本章主要介绍了函数的基本概念,包括如何定义函数、如何调用用户自定义函数,以及函数调用过程中局部框架和全局框架如何共同构成环境。

函数内部的名称具有局部作用域,不同函数调用之间的局部名称彼此独立。当局部名称与全局名称同名时,局部名称会被优先使用。

除此之外,本章还说明了运算符表达式与函数调用表达式之间的对应关系,以及嵌套结构和函数括号如何决定表达式的求值顺序。

1.4 重点摘要

Fundamentally, the qualities of good functions all reinforce the idea that functions are abstractions.

从根本上说,优秀函数的各种特征都体现并强化了“函数是一种抽象”这一思想。

Don’t repeat yourself is a central tenet of software engineering.

“不要重复自己”是软件工程的核心原则之一。

Multiple fragments of code should not describe redundant logic.

多段代码不应该描述重复的逻辑。

Instead, that logic should be implemented once, given a name, and applied multiple times.

相反,这些逻辑应该只实现一次,为它命名,然后在需要时多次使用。

If you find yourself copying and pasting a block of code, you have probably found an opportunity for functional abstraction.

如果你发现自己正在复制、粘贴一段代码,那么你很可能已经找到了进行函数抽象的机会。

Implement once, name it, and apply it multiple times.

只实现一次,为它命名,然后多次使用。

Functions should be defined generally.

函数应该尽可能通用地定义。

These guidelines improve the readability of code, reduce the number of errors, and often minimize the total amount of code written.

这些准则能够提高代码的可读性、减少错误,并且通常还能减少代码总量。

Decomposing a complex task into concise functions is a skill that takes experience to master.

将复杂任务分解成简洁函数的能力,需要通过经验逐渐掌握。

A function definition will often include documentation describing the function, called a docstring.

函数定义通常会包含用于描述函数的文档,这种文档称为文档字符串。

When you call help with the name of a function as an argument, you see its docstring.

当你把函数名作为参数传递给 help 时,就可以查看该函数的文档字符串。

Code is written only once, but often read many times.

代码通常只写一次,却经常要被阅读很多次。

These comments don’t ever appear in Python’s help, and they are ignored by the interpreter.

这些注释不会出现在 Python 的 help 信息中,也会被解释器忽略。

They exist for humans alone.

它们只为人类读者服务。

In Python, we can provide default values for the arguments of a function.

在 Python 中,我们可以为函数参数提供默认值。

When calling that function, arguments with default values are optional.

调用函数时,带有默认值的参数是可选的。

Some values that never change, such as the fundamental constant k, can be bound in the function body or in the global frame.

有些永远不会改变的值,例如基本常数 k,可以绑定在函数体中或全局帧中。

Each function should have exactly one job.

每个函数只应该负责一项工作。

Don’t repeat yourself.

不要重复自己。

Functions should be defined generally.

函数应该尽可能通用地定义。

Default arguments are optional and can be overridden by the caller.

默认参数是可选的,并且可以由调用者覆盖。

1.4 个人理解与感悟

这一章的内容主要比较简单,就是教你如何才能设计好一个函数,以及设计函数的一些规范。

其实这一章内容没什么可讲的,因为从这个摘录中,你其实就已经能够比较完整地将这一章内容概括住了。

3.3 重点摘要

Raising an exception is a technique for interrupting the normal flow of execution in a program.

引发异常是一种中断程序正常执行流程的技术。

An exception can be handled by an enclosing try statement.

异常可以由外层的 try 语句处理。

Exceptions are another technique that help us as programs to separate the concerns of our program into modular parts.

异常机制还可以帮助我们将程序中的不同关注点分离成模块化的部分。

3.3 个人理解与感悟

对于可控、明确、简单的条件,提前验证;对于执行过程中才能发现的失效,以及需要跨层传递的错误,使用异常。

异常机制允许程序将异常情况从正常执行流程中分离出来,并在合适的层次对特定错误进行处理、恢复、记录或转换。这样既能避免局部可恢复的问题直接中断整个程序,也能让真正无法处理的错误及时暴露出来。