如何用 Python 设计类?

在我之前的问题 用来检测爪子用爪子夹住脚趾上,我得到了一些非常棒的帮助,但是所有这些解决方案一次只对一个测量起作用。

现在我的数据 包括:

  • 约30只狗;
  • 每个有24个测量值(分为几个子组) ;
  • 每个测量至少有4个触点(每个爪子一个)和
    • 每个触点分为5个部分
    • 有几个参数,如接触时间,位置,总力等。

alt text

显然,将所有内容都集中到一个大对象中并不能解决问题,所以我认为我需要使用类来代替当前大量的函数。但是,尽管我已经阅读了学习 Python 中关于类的一章,我还是没有将它应用到我自己的代码(GitHub 链接)中

我也觉得这是相当奇怪的处理所有的数据 每个时,我想得到一些信息。一旦我知道了每只爪子的位置我就没有理由再计算一遍了。此外,我想比较同一只狗的所有爪子,以确定哪个接触属于哪个爪子(前/后,左/右)。如果我继续只使用函数,这将变得一团糟。

因此,现在我正在寻找关于如何创建类的建议,这些类可以让我以一种合理的方式处理数据(链接到一只狗的压缩数据)。

71768 次浏览

面向对象设计的全部思想就是让代码映射到问题,所以,例如,当你想要狗的第一个脚步声时,你可以这样做:

dog.footstep(0)

现在,您可能需要读入原始数据文件并计算脚步位置。所有这些都可以隐藏在 foostep ()函数中,以便它只发生一次。比如:

 class Dog:
def __init__(self):
self._footsteps=None
def footstep(self,n):
if not self._footsteps:
self.readInFootsteps(...)
return self._footsteps[n]

[这现在是一种缓存模式。它第一次去读取脚步数据时,接下来的次数只是从 self _ feet 获取。]

但是,是的,正确的 OO 设计可能是棘手的。更多地考虑您想要对数据做的事情,这将告诉您需要对哪些类应用哪些方法。

如何设计类。

  1. 写下单词。你开始这样做。有些人不这样做,并想知道为什么他们有问题。

  2. 将你的单词集扩展成关于这些对象将要做什么的简单陈述。也就是说,写下你们在这些东西上要做的各种计算。您的短名单30狗,24测量,4接触,和几个“参数”每接触是有趣的,但只是故事的一部分。你的“每个爪子的位置”和“比较同一只狗的所有爪子,以确定哪个接触属于哪个爪子”是物体设计的下一步。

  3. 在名词下面划线。说真的。有些人对此争论不休,但我发现对于首次使用 OO 的开发人员来说,这很有帮助。在名词下面划线。

  4. 复习名词。像“参数”和“测量”这样的通用名词需要被特定的、具体的名词所替代,这些名词适用于你的问题领域中的问题。细节有助于澄清问题。泛型只是省略了细节。

  5. 对于每个名词(“ contact”、“ paw”、“ dog”等) ,写下该名词的属性以及该对象参与的动作。别走捷径。每一个属性。例如,“数据集包含30只狗”就很重要。

  6. 对于每个属性,确定它是与已定义名词的关系,还是与其他类型的“原始”或“原子”数据(如字符串、浮点数或不可约数据)的关系。

  7. 对于每个动作或操作,你必须确定哪些名词有责任,哪些名词仅仅参与。这是一个“可变性”的问题。有些对象会更新,有些则不会。可变对象必须对它们的变化负全部责任。

  8. 此时,您可以开始将名词转换为类定义。一些集合名词是列表、字典、元组、集合或命名元组,您不需要做很多工作。其他类更复杂,要么是因为复杂的派生数据,要么是因为执行了某些更新/变异。

不要忘记使用 unittest 单独测试每个类。

而且,没有法律规定类必须是可变的。例如,在您的例子中,几乎没有可变数据。您所拥有的是由源数据集的转换函数创建的派生数据。

以下建议(类似于@S. Lott 的建议)摘自 初级 Python: 从初学者到专业人员这本书

  1. 写下你的问题的描述(问题应该做什么?)。在所有的名词、动词和形容词下面划线。

  2. 检查名词,寻找潜在的类别。

  3. 通过动词,寻找潜在的方法。

  4. 仔细研究形容词,寻找潜在的属性

  5. 为类分配方法和属性

为了完善这个课程,本书还建议我们可以做以下几件事:

  1. 写下(或构想)一套 用例-你的程序如何使用的场景。尽量覆盖所有功能。

  2. 一步一步地仔细考虑每个用例,确保我们需要的所有东西都被覆盖。

我喜欢 TDD 方法。 因此,开始编写测试,您想要的行为是什么。然后编写可以通过的代码。此时,不要太担心设计,只需要得到一个测试套件和通过测试的软件。不要担心,如果您最终只得到一个大而丑陋的类,其中包含复杂的方法。

有时,在这个初始过程中,您会发现一个行为很难测试,需要分解,只是为了可测试性。这可能暗示有必要使用单独的类。

然后是有趣的部分,重构。当你有了可以工作的软件后,你可以看到复杂的部分。通常会出现一些小的行为,建议创建一个新类,但如果没有,那么就寻找简化代码的方法。提取服务对象和值对象。简化你的方法。

如果您正确地使用了 git (您正在使用 git,不是吗?),您可以在重构期间非常快速地尝试某些特定的分解,然后放弃它,如果它不能简化事情,则返回。

通过首先编写测试过的工作代码,您应该能够更深入地了解问题领域,而这是通过设计优先的方法无法轻易获得的。编写测试和代码会让你越过“我从哪里开始”的麻痹。

写出你的名词,动词,形容词是一个很好的方法,但是我更喜欢把课堂设计看作是问问题 应该隐藏什么数据

假设你有一个 Query对象和一个 Database对象:

Query对象将帮助您创建和存储查询—— store,这里的关键是,因为函数可以帮助您轻松地创建查询。也许你可以留下来: Query().select('Country').from_table('User').where('Country == "Brazil"')。语法并不重要,那是你的工作!关键是对象帮助你 藏点东西,在这里是存储和输出查询所需的数据。对象的力量来自于使用它的语法(在这种情况下是一些巧妙的链接) ,并且不需要知道它存储了什么来使它工作。如果操作正确,Query对象可以输出对多个数据库的查询。它在内部存储特定的格式,但在输出时可以轻松地转换为其他格式(Postgres、 MySQL、 MongoDB)。

现在让我们考虑一下 Database对象。这是用来藏什么的?很明显,它不能存储数据库的全部内容,因为这就是为什么我们有一个数据库!那重点是什么?目标是从使用 Database对象的人那里获得 隐藏数据库的工作原理。好的类可以简化操作内部状态时的推理。对于这个 Database对象,您可以隐藏网络调用的工作方式,或者批处理查询或更新,或者提供一个缓存层。

问题是这个 Database对象是巨大的。它代表了如何访问数据库,所以在掩护下它可以做任何事情。显然,依赖于您的系统,网络、缓存和批处理都很难处理,因此将它们隐藏起来会非常有帮助。但是,正如许多人会注意到的那样,数据库非常复杂,离原始数据库调用越远,就越难调优性能并理解其工作原理。

这是面向对象程序设计的基本权衡。如果你选择了正确的抽象,它使编码更简单(字符串,数组,字典) ,如果你选择了一个太大的抽象(数据库,电子邮件管理器,网络管理器) ,它可能会变得太复杂,以至于无法真正理解它是如何工作的,或者期望什么。目标是 隐藏复杂性,但是需要一些复杂性。一个很好的经验法则是从避免使用 Manager对象开始,而是创建类似于 structs的类——它们所做的只是保存数据,使用一些助手方法来创建/操作数据,使您的生活更加轻松。例如,在 EmailManager的例子中,从一个名为 sendEmail的函数开始,该函数接受一个 Email对象。这是一个简单的起点,代码非常容易理解。

至于您的示例,考虑一下需要将哪些数据放在一起以计算您要查找的内容。例如,如果您想知道动物走了多远,您可以使用 AnimalStepAnimalTrip(Animalstep 的集合)类。现在每个 Trip 都有了所有的 Step 数据,那么它应该能够弄清楚关于它的东西,也许 AnimalTrip.calculateDistance()是有意义的。

在浏览了您的链接代码之后,在我看来,在这一点上,您最好使用 没有设计 Dog 类。相反,您应该使用 熊猫数据框架。数据框架是一个包含列的表。您的数据框架将有如下列: dog_idcontact_partcontact_timecontact_location等。 熊猫在幕后使用 Numpy 数组,它有很多方便的方法:

  • 选择一只狗,例如: my_measurements['dog_id']=='Charly'
  • 保存数据: my_measurements.save('filename.pickle')
  • 考虑使用 pandas.read_csv()而不是手动读取文本文件。