如何在Python中设计类?


143

我在以前的问题中为检测爪子中的爪子脚趾提供了非常出色的帮助,但是所有这些解决方案一次只能进行一次测量。

现在,我得到的数据包括:

  • 大约30条狗;
  • 每个都有24个测量值(分为几个子组);
  • 每次测量至少有4个接触点(每只爪子一个),并且
    • 每个联系人分为5部分,
    • 具有几个参数,例如接触时间,位置,总力等。

替代文字

显然,将所有内容粘贴到一个大对象中并不会减少它,因此我认为我需要使用类而不是当前的许多函数。但是,即使我已经阅读了学习Python的有关类的章节,也无法将其应用于我自己的代码(GitHub链接

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

因此,现在我正在寻找有关如何创建类的建议,这些类将使我能够以明智的方式处理我的数据(链接到一只狗的压缩数据)。


4
您可能还需要考虑使用数据库(例如sqlite:docs.python.org/library/sqlite3.html)。您可以编写一个程序,该程序读取巨大的数据文件并将其转换为数据库表中的行。然后,作为第二阶段,您可以编写程序,将数据从数据库中提取出来以进行进一步的分析。
unutbu

你是说我在这里问 @ubutbu吗?我正在计划这样做,但是首先我希望能够以一种更有条理的方式处理所有数据
Ivo Flipse 2010年

Answers:


434

如何设计课程。

  1. 写下单词。您开始这样做。有些人没有,不知道为什么会有问题。

  2. 将您的词汇集扩展为有关这些对象将要做什么的简单说明。也就是说,写下您将要在这些事情上进行的各种计算。您的30条狗的清单,24条测量值,4个联系人以及每个联系人几个“参数”很有趣,但这只是故事的一部分。您的“每个爪子的位置”和“比较同一只狗的所有爪子,以确定哪个联系人属于哪个爪子”是对象设计的下一步。

  3. 在名词下划线。说真的 一些人争论这种方法的价值,但是我发现对于初次面向对象的开发人员来说,它有所帮助。在名词下划线。

  4. 查看名词。诸如“参数”和“度量”之类的通用名词需要替换为在问题域中适用于您的问题的特定,具体名词。细节有助于澄清问题。泛型只是忽略细节。

  5. 对于每个名词(“接触”,“爪子”,“狗”等),写下该名词的属性以及该对象参与的动作。不要捷径。每个属性。例如,“数据集包含30条狗”很重要。

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

  7. 对于每个动作或操作,您必须确定哪个名词有责任,哪些名词仅参与其中。这是“可变性”的问题。有些对象得到更新,而另一些则没有。可变对象必须对其突变负全部责任。

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

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

另外,没有法律规定班级必须是可变的。例如,就您而言,您几乎没有可变数据。您所拥有的是派生数据,这些数据是通过转换功能从源数据集中创建的。


24

以下建议(类似于@ S.Lott的建议)来自《Beginning Python:从新手到专业》一书

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

  2. 遍历名词,寻找可能的类别。

  3. 遍历动词,寻找可能的方法。

  4. 浏览形容词,寻找潜在的属性

  5. 将方法和属性分配给您的类

为了完善课堂,本书还建议我们可以执行以下操作:

  1. 写下(或构想)一组用例,以了解如何使用程序。尝试涵盖所有功能。

  2. 逐步思考每个用例,确保涵盖了我们所需的一切。


最好有一些我们应该写的句子的例子。
endolith '18

14

我喜欢TDD方法...因此,首先针对所需的行为编写测试。并编写通过的代码。在这一点上,不必太担心设计,只需获得通过测试的套件和软件即可。如果您最终遇到一个丑陋的类,并且使用复杂的方法,请不要担心。

有时,在此初始过程中,您会发现难以测试且需要分解的行为(仅出于可测试性)。这可能暗示需要单独的类。

然后是有趣的部分...重构。使用了可用的软件后,您可以看到复杂的部分。通常,很少有行为的迹象,这表明有一个新的类,但如果没有,则只寻找简化代码的方法。提取服务对象和值对象。简化您的方法。

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

通过首先编写经过测试的工作代码,您应该获得对问题域的深入了解,而这些问题是设计优先方法无法轻易实现的。编写测试和代码使您摆脱“我从哪里开始”的瘫痪。


1
我也同意这个答案,但是如果要由几个团队成员并行处理问题,则分解问题并确定可能的类(即,做“足够”的软件体系结构)可能非常有用。
本·史密斯

3

OO设计的整个思想是使您的代码映射到您的问题,因此,例如,当您想要一只狗的第一个足迹时,您可以执行以下操作:

dog.footstep(0)

现在,对于您的情况,可能需要读取原始数据文件并计算足迹位置。所有这些都可以隐藏在footstep()函数中,以便仅发生一次。就像是:

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

[现在这是一种缓存模式。第一次读取足迹数据,随后又从self._footsteps获取。]

但是,是的,正确设计OO设计可能很棘手。多想想您要对数据执行的操作,这将告诉您将什么方法应用于什么类。


2

写出您的名词,动词,形容词是一种很好的方法,但是我更倾向于将类设计看作是询问应该隐藏哪些数据的问题?

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

Query对象将帮助您创建和存储查询-存储是此处的关键,因为函数可以帮助您轻松创建一个查询。也许您可以留下:Query().select('Country').from_table('User').where('Country == "Brazil"')。语法无关紧要-这就是您的工作!-关键是对象可以帮助您隐藏某些东西,在这种情况下,是存储和输出查询所必需的数据。对象的强大功能来自使用它的语法(在这种情况下,是一种巧妙的链接),并且不需要知道它存储了什么才能使其工作。如果操作正确,则该Query对象可以输出对多个数据库的查询。它在内部将存储特定格式,但在输出时可以轻松转换为其他格式(Postgres,MySQL,MongoDB)。

现在让我们仔细考虑一下Database对象。这个藏起来什么?显然,它不能存储数据库的全部内容,因为这就是我们拥有数据库的原因!那有什么意义呢?目的是向使用对象的人员隐藏数据库的工作方式Database。好的类将在处理内部状态时简化推理。对于此Database对象,您可以隐藏网络调用的工作方式,或批处理查询或更新,或提供缓存层。

问题是这个Database对象很大。它代表了如何访问数据库,因此它可以做任何事情。显然,根据系统的不同,很难进行联网,缓存和批处理,因此将它们隐藏起来将非常有帮助。但是,正如许多人会注意到的那样,数据库异常复杂,而且与原始DB调用之间的距离越远,调整性能和理解事情的工作就越困难。

这是OOP的基本权衡。如果选择正确的抽象,它会使编码更简单(字符串,数组,字典),如果选择的抽象太大(数据库,EmailManager,NetworkingManager),则可能变得太复杂而无法真正了解其工作原理或如何处理。期望。目的是隐藏复杂性,但是一定要复杂。一个好的经验法则是从避免Manager对象开始,而是创建类似的类structs-它们所做的只是保存数据,并使用一些辅助方法来创建/处理数据,从而使您的生活更轻松。例如,在以EmailManager调用sendEmail一个Email对象的函数开始的情况下。这是一个简单的起点,并且代码很容易理解。

对于您的示例,请考虑需要将哪些数据组合在一起以计算所需的内容。例如,如果您想知道一只动物走了多远,您可以拥有AnimalStepAnimalTrip(收集AnimalSteps)类。既然每个Trip都具有所有Step数据,那么它应该能够弄清楚它的内容,也许AnimalTrip.calculateDistance()是有道理的。


2

浏览了链接的代码后,在我看来,最好不要在此时设计Dog类。相反,您应该使用Pandasdataframes。数据框是带有列的表。您数据帧都会有这样的栏目:dog_idcontact_partcontact_timecontact_location,等大熊猫在后台使用numpy的阵列,它已经为你许多方便的方法:

  • 通过例如选择一只狗: my_measurements['dog_id']=='Charly'
  • 保存数据: my_measurements.save('filename.pickle')
  • 考虑使用pandas.read_csv()而不是手动读取文本文件。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.