【问题标题】:Python OO program structure planningPython OO程序结构规划
【发布时间】:2017-05-30 09:05:20
【问题描述】:

我是 OOP 的初学者,我想创建一个包含三个类 A、B 和 C 的程序。该类的每个实例都由一组特征 Achar1、Achar2 等定义。

该程序应该创建 uses 包括 A 的元素、B 的元素和 C 的元素以及开始和结束日期。 A 和 B 有子类,因此 A 的某些元素只能连接到 B 的某些元素。程序的主要功能是列出 A、B 和 C 的元素及其属性,列出 uses 并建议新的 uses 使用最少的类实例以避免重复。

我的第一直觉是使用三个字典 A、B 和 C 和一个字典来使用。这似乎直观且易于导入/导出到 json 或类似文件中。我试图重写它以使用类,但我看不出这样做有什么好处:迭代类的实例很难(?),而且它们基本上是字典,因为这些数据不需要太多其他东西。

我做错了什么?从字典切换到对象可以得到什么?

编辑:有问题的代码(基于字典)是here。第一个版本,我试图使其面向对象是here

【问题讨论】:

  • 从字典切换到对象可以获得什么? 方法中包含的业务逻辑。字典只是数据。但这并不意味着你必须使用对象,函数式编程是一个完美的范例。如果您觉得只需要字典、列表和一些函数就可以完成所有工作,那么 FP 是一个不错的选择。
  • @ŁukaszRogalski 唯一的问题是我习惯于使用非常丑陋的代码进行面向数据的编程(我已经编写了相当多的 FORTRAN77 代码......)并尝试学习一些不同的、更清晰的东西,更容易吗?

标签: python oop data-structures project-planning


【解决方案1】:

从字典切换到对象可以得到什么?

这是一个相当广泛的问题。

在 Python 中,一个对象在语义上等同于字典是对的,因为一个对象几乎等同于它的__dict__ 属性(我不会在这里详细说明这个“几乎”,因为它离主题很远)。

我看到使用类而不是字典的两个主要好处:抽象和舒适。

抽象

当您处于设计阶段时,特别是对于中短篇程序,您通常希望在思考的同时编写代码的骨架。

在需要对交互进行建模的情况下,在课堂上思考更自然,因为它介于口语和编程之间。

这使您更容易理解您自己的问题。此外,它还大大提高了代码的可读性,因为它看起来很自然,即使读者对您的代码一无所知。

这为您带来了继承和多态等概念,丰富了 OOP 提供的抽象。

舒适

Python 的众多优势之一是它的data model。大量的魔法方法和属性允许您使用非常简单的语法。另外,它可以代替你处理一些操作。

以下是Python中的命令式编程和面向对象编程之间的一些比较。

当然,Python 中的一切都是对象,所以即使在命令式示例中,我也会使用点调用 (foo.bar())。

文件读取

强制方式

f1 = open(in_path, 'r')
f2 = open(out_path, 'w')
for line in f1:
    processed = process(line)
    f2.write(processed)

# Oops, I forgot to close my files...

面向对象的方式

with open(in_path, 'r') as f1, open(out_path, 'w') as f2:    
    for line in f1:
        processed = process(line)
        f2.write(processed)

# I don't have to close my files, Python did it for me

请注意,for line in f 是 Python 面向对象数据模型的广泛使用。想象一下,如果不存在这种语法会很痛苦(好吧,只需在 C 中尝试一下)。

空性测试

强制方式

if len(some_list) == 0:
    print("empty list")

面向对象的方式

if some_list:
    print("empty list")

对序列的迭代

强制方式

i = 0
while i < len(some_sequence):
    print(some_sequence[i])
    i += 1

面向对象的方式

for elt in some_sequence:
    print(elt)

但是这个数据模型的真正优势在于它可以让你重新定义很多神奇的属性。例如,您可以通过实现__lt____le__ 等来使复杂的事物具有可比性,这将重新定义&lt;&lt;= 等的行为。此后,minmaxsort 等内置函数将了解如何比较您的对象。

也就是说,在很多情况下,仅使用字典就足够了。

归根结底,OOP 只是一种范式,命令式编程也可以正常工作。

【讨论】:

  • 并不是我不认为OOP不适合我,我只是不习惯,不明白我能从中得到什么。将数据导入到一个类中并不像仅仅加载 json 那样明显,实际上加载是中间步骤。导出也是一样的。我需要创建自己的单独对象列表,以便我可以遍历它们,这是通过遍历字典等给出的。
  • 将数据导入类不如“仅仅”加载 json 明显是不正确的。嗯,因人而异。我个人更喜欢把这样复杂的方法放在一个类后面,因为一旦写了,我就可以忘记它。然后,我就写d = DataManager(),然后d.load(data),舒舒服服地依靠过去的自己。 OOP 中的代码可能更长或更重(可能),但它更易于使用和维护。
【解决方案2】:

tl;dr 答案是多态性

类本质上并不比字典好,只是适用于不同的问题领域。如果您想创建 3 个行为类似于 dicts 的对象,那么只需使用 dicts。因此,如果 A、B 和 C 是 dicts 并且可以解决您的问题,那么您就完成了:dicts 是正确的选择。

如果它们实际上是具有不同数据结构和可能完全不同的实现的不同事物,那么 dicts 就不是正确的答案。

然而,在这种情况下(这正是类可以帮助您的地方),类提供了多态性来帮助您解决所陈述的问题。在这里,多个对象虽然属于不同的类,但每个对象都可以以自己的方式响应相同的方法。

因此,您可以为 A、B 和 C 类实现“使用”方法。这些类的对象现在将响应该方法,即使它们的其余实现完全不同。

这在基本类的__str__ 函数中最容易表示。

>>> x = 1
>>> x.__str__()
'1'
>>> x = "hello"
>>> x.__str__()
'hello'

没有人会混淆整数函数(即做数学)和字符串函数(即存储文本),但它们都响应相同的消息__str__。这就是为什么以下工作:

>>> y = 1
>>> z = "hello"
>>> y.__str__() + z.__str__()
'1hello'

因此,在您的情况下,您可以拥有三个具有完全不同实现的类 A、B 和 C,但给它们每一个类似的方法“使用”。然后当你问他们所有的“用途”时,他们都会以同样的方式回答,你可以编制你的用途清单。

您的代码充满了长长的“case”语句,这里是关于如何使用多态性来删除它们的精彩讨论:

Ways to eliminate switch in code

您应该能够通过消除所有控制语句 (ifs) 并将它们替换为向对象发送消息和让对象弄清楚要做什么。您的对象应具有“import”、“export”和“add”等方法。

【讨论】:

    【解决方案3】:

    如果你只是从 dict 扩展每个类并个性化他们的行为呢?

    class classA(dict):
        '''Some personalization here...'''
        def __init__(self, **kwargs):
            pass
    

    我的意思是...它们仍将是字典,将用作字典,但您可以定义自己的一些方法。

    编辑:

    好的...我阅读了您的代码并尝试从中执行一些 OOP。并不是有史以来最好的 OOP,但您可以理解。代码更干净、更容易调试、更容易扩展、更容易维护等等……

    这里是保管箱文件夹: https://www.dropbox.com/sh/8clyys2gcnodzkq/AACXhPnCW5d9fx0XY6Y-y_6ca?dl=0

    你怎么看?

    【讨论】:

    • 嗯,这很好,我做了一些非常相似的事情,但发现与纯字典相比,这没有给我任何帮助。为什么要在我提出的问题中使用类?
    猜你喜欢
    • 2013-08-19
    • 1970-01-01
    • 2011-03-15
    • 1970-01-01
    • 1970-01-01
    • 2010-11-11
    • 2016-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多