问题:如何在Python中设计类?
我在以前的问题中为检测爪子中的爪子和脚趾提供了非常出色的帮助,但是所有这些解决方案一次只能进行一次测量。
现在,我得到的数据包括:
- 大约30条狗;
- 每个都有24个测量值(分为几个子组);
- 每次测量至少有4个接触点(每只爪子一个),并且
- 每个联系人分为5部分,
- 具有几个参数,例如接触时间,位置,总力等。
显然,将所有内容粘贴到一个大对象中并不会减少它,因此我认为我需要使用类而不是当前的许多函数。但是,即使我已经阅读了学习Python的有关类的章节,也无法将其应用于我自己的代码(GitHub链接)
我也觉得每次我想获取一些信息时处理所有数据都是很奇怪的。一旦知道了每个爪子的位置,就没有理由再进行计算了。此外,我想比较同一只狗的所有爪子,以确定哪个接触属于哪个爪子(前/后,左/右)。如果我继续只使用函数,那将变得一团糟。
因此,现在我正在寻找有关如何创建类的建议,这些类将使我能够以明智的方式处理我的数据(链接到一只狗的压缩数据)。
回答 0
如何设计类。
写下单词。您开始这样做。有些人没有,不知道为什么会有问题。
将您的词汇集扩展为有关这些对象将要做什么的简单说明。也就是说,写下您将要在这些事情上进行的各种计算。您的30条狗的清单,24条测量值,4个联系人以及每个联系人几个“参数”很有趣,但这只是故事的一部分。您的“每个爪子的位置”和“比较同一只狗的所有爪子,以确定哪个联系人属于哪个爪子”是对象设计的下一步。
在名词下划线。说真的 一些人参数这种方法的价值,但是我发现对于初次面向对象的开发人员来说,它有所帮助。在名词下划线。
查看名词。诸如“参数”和“度量”之类的通用名词需要替换为在问题域中适用于您的问题的特定,具体名词。细节有助于澄清问题。泛型只是忽略细节。
对于每个名词(“接触”,“爪子”,“狗”等),写下该名词的属性以及该对象参与的动作。不要捷径。每个属性。例如,“数据集包含30条狗”很重要。
对于每个属性,请确定这是与已定义名词的关系,还是与其他类型的“原始”或“原子”数据(如字符串或浮点数或不可约数)的关系。
对于每个动作或操作,您必须确定哪个名词有责任,哪些名词仅参与其中。这是“可变性”的问题。有些对象得到更新,而另一些则没有。可变对象必须对其突变负全部责任。
此时,您可以开始将名词转换为类定义。一些集合名词是列表,字典,元组,集合或命名元组,您不需要做很多工作。由于复杂的派生数据或执行的某些更新/变异,其他类则更为复杂。
不要忘记使用unittest单独测试每个类。
另外,没有法律规定Class必须是可变的。例如,就您而言,您几乎没有可变数据。您所拥有的是派生数据,这些数据是通过转换功能从源数据集中创建的。
回答 1
以下建议(类似于@ S.Lott的建议)来自《Beginning Python:从新手到专业》一书
写下您的问题的描述(问题应该做什么?)。在所有名词,动词和形容词下划线。
遍历名词,寻找可能的类别。
遍历动词,寻找可能的方法。
浏览形容词,寻找潜在的属性
将方法和属性分配给您的类
为了完善课堂,本书还建议我们可以执行以下操作:
写下(或构想)一组用例,以了解如何使用程序。尝试涵盖所有功能。
逐步思考每个用例,确保涵盖了我们所需的一切。
回答 2
我喜欢TDD方法…因此,首先针对所需的行为编写测试。并编写通过的代码。在这一点上,不必太担心设计,只需获得通过测试的套件和软件即可。如果您最终遇到一个丑陋的类,并且使用复杂的方法,请不要担心。
有时,在此初始过程中,您会发现难以测试且需要分解的行为(仅出于可测试性)。这可能暗示需要单独的类。
然后是有趣的部分…重构。使用了可用的软件后,您可以看到复杂的部分。通常,很少有行为的迹象,这表明有一个新的类,但如果没有,则只寻找简化代码的方法。提取服务对象和值对象。简化您的方法。
如果您正确使用了git(不是,您正在使用git吗?),则可以在重构过程中非常快速地尝试进行某些特定的分解,然后放弃它,如果它不能简化事情,请还原并返回。
通过首先编写经过测试的工作代码,您应该获得对问题域的深入了解,而这些问题是设计优先方法无法轻易实现的。编写测试和代码使您摆脱“我从哪里开始”的瘫痪。
回答 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设计可能很棘手。多想想您要对数据执行的操作,这将告诉您将什么方法应用于什么类。
回答 4
写出您的名词,动词,形容词是一种很好的方法,但是我更倾向于将类设计看作是询问应该隐藏哪些数据的问题?
假设您有一个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
对象的函数开始的情况下。这是一个简单的起点,并且代码很容易理解。
对于您的示例,请考虑需要将哪些数据组合在一起以计算所需的内容。例如,如果您想知道一只动物走了多远,您可以拥有AnimalStep
和AnimalTrip
(收集AnimalSteps)类。既然每个Trip都具有所有Step数据,那么它应该能够弄清楚它的内容,也许AnimalTrip.calculateDistance()
是有道理的。
回答 5
浏览了链接的代码后,在我看来,最好不要在此时设计Dog类。相反,您应该使用Pandas和dataframes。数据框是带有列的表。您数据帧都会有这样的栏目:dog_id
,contact_part
,contact_time
,contact_location
,等大熊猫在后台使用numpy的阵列,它已经为你许多方便的方法:
- 通过例如选择一只狗:
my_measurements['dog_id']=='Charly'
- 保存数据:
my_measurements.save('filename.pickle')
- 考虑使用
pandas.read_csv()
而不是手动读取文本文件。