Python 风格分解
目录
软件工程危机
假设你需要完成一个 500 行、1000 行、10,000 行或 100,000 行的编码项目。你认为需要多少小时才能完成?
在计算发展的早期,随着计算机功能越来越强大,出现了一种令人不安的模式。任何人都可以完成一个 500 行的项目,但显然项目在规模上确实扩大得非常好。一个 10 倍大的项目需要 10 倍以上的时间才能完成。非常大的项目似乎永远无法完成。该项目可能会被放弃,或者用户可能不得不忍受未完成的版本。当我尝试在线填写表格并做了一些它不喜欢的事情时,我想到这一点,系统会让我回到一个空白表格重新输入信息。那一刻我停下来意识到,不知何故,世界上很多软件并没有真正完成。
软件成本 - N 2
根据经验,完成一个 N 行软件项目的难度似乎与 N 2成正比。
N2曲线变得非常陡峭,随着线路数量的增加,项目的成本也变得非常高。超过某个点,项目几乎不可能完成。然而,你生活在一个拥有许多庞大且功能齐全的项目的世界里。CS 如何摆脱 N2陷阱?
分而治之 - 功能 - 模块化
CS 有一个技巧可以避免 N 2陷阱。假设我们要编写这个 500 行的程序。我们该怎么做?
一个大 main()
最简单的方法是将所有代码写成一个 500 行的 main() 函数。这是一个糟糕的策略,会导致N2曲线最差的结果- 太多代码都集中在一个地方
功能分解
关键步骤不是开发 500 行项目。开发一系列 50 行函数 - 每个都很好而且便宜 - 并将它们捆绑在一起以形成 500 行。或者换句话说,留在 N 2曲线上。
这就是为什么函数是每种编程语言的核心部分。编程总是以这种规模进行,试图保持在曲线上。努力使程序的各个部分彼此分离也称为“模块化”策略。
功能协作 - 数据黑匣子
我们可以将程序划分为多个函数,但这些函数之间如何协作呢?使用黑盒模型,我们使用每个函数的输入和输出数据作为其与所有其他函数的接触点。
另外:虽然这似乎是一个细节,但保持严格的数据输入和数据输出结构却很重要。如果允许函数任意访问其他函数使用的数据,实际上它会将这些函数的行混合在一起。在 Python 和其他语言中,可以使用“全局变量”在函数之间移动数据,而不仅仅是在调用/返回边界。我们在 CS106A 中永远不会这样做,而是专注于适当的函数调用、黑盒模型,使函数彼此分离。
整个程序图-数据管道
如果所有功能都是独立的,它们如何相互协作?黑盒模型可以帮到你 - 我们将一个功能的输出连接到下一个功能的输入。这些功能是独立的,但可以协同工作。
您可以将整个程序视为一个函数管道,比如,首先接收一个文本文件的数据,每个函数返回一些精炼版本的数据,直到我们最后得到漂亮的图表或结论,然后获得崇拜的推文!
这些功能都是独立的,但它们共同作用来解决整个问题。它们的输入/输出交互尽可能保持狭窄和简单。
黑盒——抽象与实现
您已经熟悉了函数的输入/输出框架。这里我们将添加 CS 术语“抽象”和“实现”。这些概念对于计算机系统至关重要,但您可能会发现它们对生活的许多方面都很有帮助。
黑匣子 1 - 抽象
函数的抽象就是它所完成的事情——它需要什么作为输入,以及它会产生什么作为输出。我们可以将其视为函数的“契约”:需要输入什么。函数承诺提供什么。抽象契约基本上也是函数顶部三重引号“Pydoc”中写的内容。
黑匣子 2 - 实施
函数的实现细节是函数中执行实际工作的所有代码和复杂性。“细节”一词有时被用作函数中隐藏的所有实现特性的总称。
通常,函数的抽象与其实现相比相对简单。
关键:调用函数 - 抽象
您需要知道什么才能正确调用函数?只需要抽象。实现可以隐藏在函数内部。我们的策略是将“实现细节”隐藏在函数内部,这样程序的其余部分就不需要知道或依赖它。这就是我们对抗 N 2曲线的方法。
分解、抽象、实现的示例
1. 函数1()
假设你正在编写一个大程序,现在是时候开始function1():
定义函数1(s):
"""给定字符串 s ..."""
..很多细节..
.. 在这里 ..
处理 function1。此时,您的注意力集中在 function1 抽象上,并且您正在努力解决其实现的细节、错误和其他问题。最终,您让它完美地运行。
2. 函数2()
现在是时候处理 function2() 了,它调用 function1() 作为辅助函数。查看下面的关键行。
定义函数2(s):
“”“……””
....
part = function1(s) # 关键行
...
你在写关键行时是什么心态? function1 的抽象。不要考虑 function1 的实现细节,尽管你刚刚在写它。
每次只考虑一种实现方式
N 2陷阱告诉我们,一次性记住所有函数实现是毫无希望的。在这里,我们一次只考虑一种实现。一旦函数完成,我们只需要从抽象的角度进行工作。
不知道的力量
我们已经将一些程序复杂性划分到 function1 内部。当需要调用 function1() 时,我们可以考虑它是如何实现的。相反,我们接受不知道里面发生了什么。只需调用它,它就应该满足其契约。利用抽象来只知道我们需要什么。
这就是为什么 Python 和其他语言都有“PyDoc”文档,允许写出合同并轻松访问,这样程序员就可以访问他们需要的抽象,而不必查看实现细节。
计算机科学中的抽象
这种使用抽象、隔离单元内复杂性的技术是构建所有大型计算机系统的核心技巧。例如,在 CS106A 的后面,我们将看到如下函数调用
# 获取目录内的文件名列表
文件名 = os.listdir('下载')
# 获取当前日期和时间
现在 = datetime.now()
我们知道它的抽象os.listdir()
非常简单 - 目录(文件夹)内的字符串文件名列表。但实现...谁知道呢。os.listdir() 中的代码非常复杂,访问您的操作系统,也许访问插入计算机的 USB 密钥。调用该函数时,我们不会考虑这一点。我们假设该函数正确完成其工作并返回有效文件名字符串列表,我们只需继续使用它们。这就是方法。
乘车前往机场的抽象与实施
我们在生活中一直使用抽象——这是让多个人协调某件事的自然方式。
假设你正搭车去机场。抽象是什么?
乘车前往机场抽象 - 一个很短的清单
- 接送时间和地点。
- 还车时间和地点。
- 是否与他人共乘。
乘车去机场实施细节,不关心:
- 该车配有合金轮毂
- 汽车配备 LED 大灯
- 汽车座椅的颜色
- 司机戴帽子吗
- 汽车有足够的燃料
你确实在某种程度上关心这一点,但它被“还车时间/地点”所涵盖
重点:抽象比实现简单得多。实现中有很多抽象不需要关心的细节。调用函数只是抽象。
机制:Pydoc、Doctests
因此,抽象思想转化为设计函数的 Python 代码。选择一个好的函数名称,概括它的功能。参数列出它的输入。函数顶部的“Pydoc”用文字总结了它的抽象。它需要什么作为输入?它承诺返回什么作为输出?我们通常在这里使用“给定”一词来指代参数,例如“给定值 x 和 y,返回某些东西。”
您可以删除 PyCharm 放入的“:param s:”内容。目前很少使用该语法。您可以使用“Given x ...”Pydoc 总结抽象。
Doctests 是总结抽象的另一种方式 - 不是用文字,而是用一系列输入/输出示例。它们还有助于您调试代码。
def del_chars(s,目标):
“””
给定字符串 s 和一个“目标”字符串,
返回 s 的一个版本,其中包含所有字符
出现在已删除的目标中,例如 s 'abc'
以目标“bx”为目标,返回“ac”。
(不区分大小写)
>>> del_chars('abC','acx')
‘b’
>>> del_chars('ABc','aCx')
‘B’
>>> del_chars('','a')
“”
“””
结果 = ''
目标 = 目标.较低()
对于 s 中的 ch:
如果 ch.lower() 不在目标中:
结果 += ch
返回结果
、