【问题标题】:Most appropriate way to combine features of a class to another?将一个类的特征组合到另一个类的最合适的方法是什么?
【发布时间】:2013-08-25 22:27:57
【问题描述】:

大家好,我是新来的,但希望我的问题很清楚。

我的代码是用 Python 编写的。 我有一个代表一般网站的基类,这个类包含一些从网站获取数据并保存的基本方法。该类由许多其他类扩展,每个类代表一个不同的网站,每个类都持有特定于该网站的属性,每个子类都使用基类方法来获取数据。所有站点都应在其上解析数据,但许多站点共享相同的解析功能。所以我创建了几个解析类,它们包含不同解析方法的功能和属性(我有大约六个)。我开始思考将这些类与需要它们的网站类集成的最佳方式是什么。

一开始我以为每个网站类都会保存一个类变量和对应的解析器类,但后来我认为必须有更好的方法来做到这一点。

我读了一点,并认为依靠 Mixins 为每个网站集成解析器可能会更好,但后来我认为虽然这会起作用,但它并不“听起来”正确,因为网站类没有业务继承自解析器类(甚至认为它只是一个 Mixin,并不意味着完全继承类),因为除了网站使用解析器功能之外,它们没有任何关系。

然后我想我可能会依靠我看到的一些依赖注入代码来将解析器注入到每个网站,但这听起来有点矫枉过正。

所以我想我的问题基本上是,什么时候最好使用每种情况(在我的项目和任何其他项目中),因为它们都可以完成这项工作,但似乎并不是最合适的。

感谢您提供的任何帮助,我希望我很清楚。

添加一个小模拟示例来说明:

class BaseWebsite():
    def fetch(): # Shared by all subclasses websites
       ....
    def save(): # Shared by all subclasses websites
       ....

class FirstWebsite(BaseWebsite): # Uses parsing method one
    ....
class SecondWebsite(BaseWebsite): # Uses parsing method one
    ....
class ThirdWebsite(BaseWebsite): # Uses parsing method two
    ....

等等

【问题讨论】:

  • 附带说明:如果这是 Python 2.x,class BaseWebsite(): 会为您提供您不想要的旧式类;使用class BaseWebsite(object):。如果这是 Python 3.x,class BaseWebsite(): 会做正确的事,但class BaseWebsite: 也是如此,它更干净、更易读。并且更惯用。无论哪种方式,class BaseWebsite(): 看起来您都在尝试用这些括号做某事,并让读者想知道您在尝试做什么。

标签: python oop dependency-injection mixins


【解决方案1】:

我认为你的问题是你在应该使用实例的地方使用子类。

根据您的描述,每个网站都有一个类,具有一堆属性。大概您创建每个类的单例实例。在 Python 中很少有充分的理由这样做。如果每个网站需要不同的数据(基本 URL、解析器对象/工厂/函数等),您可以将其存储在实例属性中,这样每个网站都可以是同一类的实例。

如果网站确实需要,比如说,以不同的方式覆盖基类方法,那么它们是不同的类是有意义的(尽管即使在那里,您也应该考虑是否将该功能移动到外部函数或对象中由网站使用,就像您已经使用解析器一样)。但如果不是,就没有充分的理由这样做。

当然我在这里可能是错的,但是你定义了旧式类,在你的方法中留下了self 参数,谈到了类属性,并且通常使用 Java 术语而不是 Python 术语,这让我觉得这个错误不太可能发生。

换句话说,你想要的是:

class Website:
    def __init__(self, parser, spam, eggs):
        self.parser = parser
        # ...
    def fetch(self):
        data = # ...
        soup = self.parser(data)
        # ...

first_website = Website(parser_one, urls[0], 23)
second_website = Website(parser_one, urls[1], 42)
third_website = Website(parser_two, urls[2], 69105)

假设您有 20 个网站。如果您要创建 20 个子类,那么您要为每个子类编写六行样板代码,并且您可能会在细节上出错,这可能会让调试很痛苦。如果您要创建 20 个实例,这只是样板文件中的几个字符,而且出错的次数要少得多:

websites = [Website(parser_one, urls[0], 23),
            Website(parser_two, urls[1], 42),
            # ...
           ]

或者您甚至可以将数据移动到数据文件中。例如,像这样的 CSV:

url,parser,spam
http://example.com/foo,parser_one,23
http://example.com/bar,parser_two,42
…

您可以更轻松地进行编辑,甚至可以使用电子表格程序进行编辑,无需任何多余的输入。您可以通过几行代码将其导入 Python:

with open('websites.csv') as f:
    websites = [Website(**row) for row in csv.DictReader(f)]

【讨论】:

  • 由于这两种方法都可以,所以一些解释会有所帮助。
  • 好吧,起初我想使用这种方法,但后来网站变得更加复杂,首先每个网站子类都应该包含一些特定于该网站的属性,即解析器使用的 url 和信息。由于它们都共享基本的 BaseWebsite 通用功能,例如获取和保存,并且应该仅根据这些属性和特定于每个网站的一些小方法进行区分,我认为最好的方法是子类化 BaseWebsite 类,让我回到我原来的问题
  • @fred:拥有特定于网站的属性确切地是实例的用途,而不是子类。正如tom10建议的那样,我已经更新了答案以提供更多解释。
  • 嗯,我明白你的意思,但这意味着我必须保留一个字典列表或带有 URL 及其匹配解析器及其匹配属性的东西,以便我可以遍历它并将其传递给每个实例。这看起来很像一个没有方法的类,这又让我回到了原来的状态。抱歉,我正在朝那个方向发展,我只是不明白为什么我应该为这些数据保留一个单独的列表,而不是只保留在每个子类中?
  • @fred:一方面,字典比类简单得多。另一方面,如果您只是存储数据而不是代码,则可以将其存储在更易于编辑的数据文件中。例如,如果您将 URL 列表及其属性放在 CSV 文件中,您可以在您喜欢的电子表格程序中对其进行编辑,而不必为每个文件复制和粘贴一堆额外的样板,然后将 CSV 读入一个dict 在几行代码中。
猜你喜欢
  • 1970-01-01
  • 2016-10-14
  • 2020-01-12
  • 1970-01-01
  • 2018-05-09
  • 2011-10-21
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多