繁体   English   中英

如何在Python中设计类?

[英]How do I design a class in Python?

我在以前的问题上有一些非常棒的帮助来检测爪子 内的 爪子脚趾 ,但所有这些解决方案一次只能用于一次测量。

现在我有数据包含:

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

替代文字

显然将所有内容都放在一个大对象上并不会削减它,所以我认为我需要使用类而不是当前的大量函数。 但即使我已经阅读了学习Python关于类的章节,我也无法将其应用于我自己的代码( GitHub链接

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

所以现在我正在寻找关于如何创建类的建议,让我以合理的方式处理我的数据( 链接到一条狗的压缩数据 )。

如何设计课程。

  1. 写下这些话。 你开始这样做了。 有些人不知道为什么他们有问题。

  2. 将您的单词扩展为关于这些对象将要执行的操作的简单语句。 也就是说,写下你将对这些事情做的各种计算。 每个联系人的30条狗,24个测量值,4个联系人和几个“参数”的简短列表很有意思,但这只是故事的一部分。 你的“每个爪子的位置”和“比较同一只狗的所有爪子以确定哪个接触属于哪个爪子”是对象设计的下一步。

  3. 强调名词。 认真。 有些人讨论这个的价值,但我发现对于首次面向OO的开发人员来说,它有所帮助。 强调名词。

  4. 复习名词。 像“参数”和“测量”这样的通用名词需要替换为适用于问题域中问题的特定具体名词。 细节有助于澄清问题。 泛型只是简单的细节。

  5. 对于每个名词(“联系人”,“爪子”,“狗”等),记下该名词的属性以及该对象所参与的动作。 不要吝啬这一点。 每个属性。 例如,“数据集包含30只狗”很重要。

  6. 对于每个属性,确定这是与定义的名词的关系,还是某种其他类型的“原始”或“原子”数据,如字符串或浮点数或不可简化的东西。

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

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

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

此外,没有法律规定课程必须是可变的。 例如,在您的情况下,您几乎没有可变数据。 您拥有的是由源数据集中的转换函数创建的派生数据。

以下建议(类似于@ S.Lott的建议)来自于Beginning Python:From Novice to Professional

  1. 写下你的问题的描述(问题应该怎样做?)。 强调所有名词,动词和形容词。

  2. 浏览名词,寻找潜在的课程。

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

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

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

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

  1. 记下(或梦想)一组用例 - 如何使用您的程序的场景。 尽量覆盖所有功能。

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

我喜欢TDD方法......所以首先要为你想要的行为编写测试。 并编写通过的代码。 此时,不要过于担心设计,只需获得测试套件和通过的软件。 不要担心,如果你最终得到一个单一的丑陋类,复杂的方法。

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

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

如果你正确使用git(你使用的是git,不是吗?),你可以在重构期间快速尝试一些特定的分解,然后放弃它并在不简化的情况下恢复。

通过首先编写经过测试的工作代码,您应该获得对使用设计优先方法无法轻易获得的问题域的深入了解。 编写测试和代码会让你超越“我从哪里开始”瘫痪。

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可能会非常棘手。 更多地考虑您要对数据做的事情,这将告知您需要哪些方法应用于哪些类。

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

想象一下,你有一个Query对象和一个Database对象:

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

现在让我们通过Database对象进行思考。 这隐藏和存储什么? 很明显它无法存储数据库的全部内容,因为这就是我们拥有数据库的原因! 那有什么意义呢? 目标是隐藏数据库如何使用Database对象的人。 在操纵内部状态时,良好的类将简化推理。 对于此Database对象,您可以隐藏网络调用的工作方式,批量查询或更新,或提供缓存层。

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

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

至于您的示例,请考虑需要将哪些数据放在一起来计算您要查找的内容。 例如,如果您想知道动物行走的距离,您可以使用AnimalStepAnimalTripAnimalTrip集合)类。 现在每个Trip都有所有Step数据,那么它应该能够找到关于它的东西,也许AnimalTrip.calculateDistance()是有道理的。

在浏览链接代码之后,在我看来,你最好不要在此时设计Dog类。 相反,您应该使用Pandas数据帧 数据框是一个包含列的表。 您的数据dog_id将包含如下列: dog_idcontact_partcontact_timecontact_locationdog_id在幕后使用Numpy数组,它有许多方便的方法:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM